You're the computer, you sort the fields (#2641)

Change makeStructType() API for sanity. Update callers.
This commit is contained in:
Aaron Boodman
2016-09-28 15:39:52 -07:00
committed by GitHub
parent 4fd84d5141
commit 3efc6c5f7d
18 changed files with 238 additions and 270 deletions

View File

@@ -1,7 +1,7 @@
{
"name": "@attic/noms",
"license": "Apache-2.0",
"version": "60.0.1",
"version": "61.0.0",
"description": "Noms JS SDK",
"repository": "https://github.com/attic-labs/noms",
"main": "dist/commonjs/noms.js",

View File

@@ -117,7 +117,7 @@ suite('validate type', () => {
});
test('struct', () => {
const type = makeStructType('Struct', ['x'], [boolType]);
const type = makeStructType('Struct', {x: boolType});
const v = newStruct('Struct', {x: true});
assertSubtype(type, v);
@@ -178,8 +178,8 @@ suite('validate type', () => {
});
test('struct subtype by name', () => {
const namedT = makeStructType('Name', ['x'], [numberType]);
const anonT = makeStructType('', ['x'], [numberType]);
const namedT = makeStructType('Name', {x: numberType});
const anonT = makeStructType('', {x: numberType});
const namedV = newStruct('Name', {x: 42});
const name2V = newStruct('foo', {x: 42});
const anonV = newStruct('', {x: 42});
@@ -194,9 +194,9 @@ suite('validate type', () => {
});
test('struct subtype extra fields', () => {
const at = makeStructType('', [], []);
const bt = makeStructType('', ['x'], [numberType]);
const ct = makeStructType('', ['s', 'x'], [stringType, numberType]);
const at = makeStructType('', {});
const bt = makeStructType('', {x: numberType});
const ct = makeStructType('', {s: stringType, x: numberType});
const av = newStruct('', {});
const bv = newStruct('', {x: 1});
const cv = newStruct('', {x: 2, s: 'hi'});
@@ -219,22 +219,16 @@ suite('validate type', () => {
value: 1,
parents: new Set(),
});
const t1 = makeStructType('Commit',
['parents', 'value'],
[
makeSetType(makeUnionType([])),
numberType,
]
);
const t1 = makeStructType('Commit', {
parents: makeSetType(makeUnionType([])),
value: numberType,
});
assertSubtype(t1, c1);
const t11 = makeStructType('Commit',
['parents', 'value'],
[
makeSetType(makeRefType(t1)),
numberType,
]
);
const t11 = makeStructType('Commit', {
parents: makeSetType(makeRefType(t1)),
value: numberType,
});
assertSubtype(t11, c1);
const c2 = newStruct('Commit', {
@@ -253,18 +247,18 @@ suite('validate type', () => {
// x: Cycle<0>,
// y: Number,
// }
const t1 = makeStructType('', ['x', 'y'], [
makeCycleType(0),
numberType,
]);
const t1 = makeStructType('', {
x: makeCycleType(0),
y: numberType,
});
// struct {
// x: Cycle<0>,
// y: Number | String,
// }
const t2 = makeStructType('', ['x', 'y'], [
makeCycleType(0),
makeUnionType([numberType, stringType]),
]);
const t2 = makeStructType('', {
x: makeCycleType(0),
y: makeUnionType([numberType, stringType]),
});
assert.isTrue(isSubtype(t2, t1, []));
assert.isFalse(isSubtype(t1, t2, []));
@@ -273,10 +267,10 @@ suite('validate type', () => {
// x: Cycle<0> | Number,
// y: Number | String,
// }
const t3 = makeStructType('', ['x', 'y'], [
makeUnionType([makeCycleType(0), numberType]),
makeUnionType([numberType, stringType]),
]);
const t3 = makeStructType('', {
x: makeUnionType([makeCycleType(0), numberType]),
y: makeUnionType([numberType, stringType]),
});
assert.isTrue(isSubtype(t3, t1, []));
assert.isFalse(isSubtype(t1, t3, []));
@@ -288,10 +282,10 @@ suite('validate type', () => {
// x: Cycle<0> | Number,
// y: Number,
// }
const t4 = makeStructType('', ['x', 'y'], [
makeUnionType([makeCycleType(0), numberType]),
numberType,
]);
const t4 = makeStructType('', {
x: makeUnionType([makeCycleType(0), numberType]),
y: numberType,
});
assert.isTrue(isSubtype(t4, t1, []));
assert.isFalse(isSubtype(t1, t4, []));
@@ -313,27 +307,31 @@ suite('validate type', () => {
// b: Cycle<1>,
// },
// }
const tb = makeStructType('', ['b'], [
makeStructType('', ['c'], [
makeCycleType(1),
]),
]);
const tc = makeStructType('', ['c'], [
makeStructType('', ['b'], [
makeCycleType(1),
]),
]);
const tb = makeStructType('', {
b: makeStructType('', {
c: makeCycleType(1),
}),
});
const tc = makeStructType('', {
c: makeStructType('', {
b: makeCycleType(1),
}),
});
assert.isFalse(isSubtype(tb, tc, []));
assert.isFalse(isSubtype(tc, tb, []));
});
test('isSubtype compound union', () => {
const emptyStructType = makeStructType('', [], []);
const emptyStructType = makeStructType('', {});
const rt = makeListType(emptyStructType);
const st1 = makeStructType('One', ['a'], [numberType]);
const st2 = makeStructType('Two', ['b'], [stringType]);
const st1 = makeStructType('One', {
a: numberType,
});
const st2 = makeStructType('Two', {
b: stringType,
});
const ct = makeListType(makeUnionType([st1, st2]));
assert.isTrue(isSubtype(rt, ct));

View File

@@ -22,21 +22,19 @@ import Ref from './ref.js';
import {newStruct} from './struct.js';
suite('commit.js', () => {
const emptyStructType = makeStructType('', [], []);
const emptyStructType = makeStructType('', {});
test('new Commit', () => {
function assertTypeEquals(e, a) {
assert.isTrue(equals(a, e), `Actual: ${a.describe()}\nExpected ${e.describe()}`);
}
const commitFieldNames = ['meta', 'parents', 'value'];
const commit = new Commit(1, new Set());
const at = commit.type;
const et = makeStructType('Commit', commitFieldNames, [
emptyStructType,
makeSetType(makeRefType(makeCycleType(0))),
numberType,
]);
const et = makeStructType('Commit', {
meta: emptyStructType,
parents: makeSetType(makeRefType(makeCycleType(0))),
value: numberType,
});
assertTypeEquals(et, at);
// Commiting another Number
@@ -48,31 +46,34 @@ suite('commit.js', () => {
// Now commit a String
const commit3 = new Commit('Hi', new Set([new Ref(commit2)]));
const at3 = commit3.type;
const et3 = makeStructType('Commit', commitFieldNames, [
emptyStructType,
makeSetType(makeRefType(makeStructType('Commit', commitFieldNames, [
emptyStructType,
makeSetType(makeRefType(makeCycleType(0))),
makeUnionType([numberType, stringType]),
]))),
stringType,
]);
const et3 = makeStructType('Commit', {
meta: emptyStructType,
parents: makeSetType(makeRefType(makeStructType('Commit', {
meta: emptyStructType,
parents: makeSetType(makeRefType(makeCycleType(0))),
value: makeUnionType([numberType, stringType]),
}))),
value: stringType,
});
assertTypeEquals(et3, at3);
// Now commit a String with MetaInfo
const meta = newStruct('Meta', {date: 'some date', number: 9});
const metaType = makeStructType('Meta', ['date', 'number'], [stringType, numberType]);
const metaType = makeStructType('Meta', {
date: stringType,
number: numberType,
});
const commit4 = new Commit('Hi', new Set([new Ref(commit2)]), meta);
const at4 = commit4.type;
const et4 = makeStructType('Commit', commitFieldNames, [
metaType,
makeSetType(makeRefType(makeStructType('Commit', commitFieldNames, [
makeUnionType([emptyStructType, metaType]),
makeSetType(makeRefType(makeCycleType(0))),
makeUnionType([numberType, stringType]),
]))),
stringType,
]);
const et4 = makeStructType('Commit', {
meta: metaType,
parents: makeSetType(makeRefType(makeStructType('Commit', {
meta: makeUnionType([emptyStructType, metaType]),
parents: makeSetType(makeRefType(makeCycleType(0))),
value: makeUnionType([numberType, stringType]),
}))),
value: stringType,
});
assertTypeEquals(et4, at4);
});

View File

@@ -78,24 +78,23 @@ export default class Commit<T: Value> extends Struct {
// ../../go/datas/commit.go for the motivation for how this is computed.
function makeCommitType(valueType: Type<any>, parentsValueTypes: Type<any>[],
metaType: Type<any>, parentsMetaTypes: Type<any>[]): Type<StructDesc> {
const fieldNames = ['meta', 'parents', 'value'];
const parentsValueUnionType = makeUnionType(parentsValueTypes.concat(valueType));
const parentsMetaUnionType = makeUnionType(parentsMetaTypes.concat(metaType));
let parentsType;
if (equals(parentsValueUnionType, valueType) && equals(parentsMetaUnionType, metaType)) {
parentsType = makeSetType(makeRefType(makeCycleType(0)));
} else {
parentsType = makeSetType(makeRefType(makeStructType('Commit', fieldNames, [
parentsMetaUnionType,
makeSetType(makeRefType(makeCycleType(0))),
parentsValueUnionType,
])));
parentsType = makeSetType(makeRefType(makeStructType('Commit', {
meta: parentsMetaUnionType,
parents: makeSetType(makeRefType(makeCycleType(0))),
value: parentsValueUnionType,
})));
}
return makeStructType('Commit', fieldNames, [
metaType,
parentsType,
valueType,
]);
return makeStructType('Commit', {
meta: metaType,
parents: parentsType,
value: valueType,
});
}
function valueTypesFromParents(parents: Set<any>, fieldName: string): Type<any>[] {

View File

@@ -61,19 +61,16 @@ suite('Encode human readable types', () => {
});
test('struct', () => {
const type = makeStructType('S1',
['x', 'y'],
[
numberType,
stringType,
]
);
const type = makeStructType('S1', {
x: numberType,
y: stringType,
});
assertWriteType('struct S1 {\n x: Number,\n y: String,\n}', type);
});
test('list of struct', () => {
const type = makeStructType('S3', ['x'], [numberType]);
const type = makeStructType('S3', {x: numberType});
assertWriteType('List<struct S3 {\n x: Number,\n}>', makeListType(type));
});
@@ -87,20 +84,14 @@ suite('Encode human readable types', () => {
// }
// }
const a = makeStructType('A',
['b', 'c', 'd'],
[
makeCycleType(0),
makeListType(makeCycleType(0)),
makeStructType('D',
['e', 'f'],
[
makeCycleType(0),
makeCycleType(1),
]
),
]
);
const a = makeStructType('A', {
b: makeCycleType(0),
c: makeListType(makeCycleType(0)),
d: makeStructType('D', {
e: makeCycleType(0),
f: makeCycleType(1),
}),
});
assertWriteType(`struct A {
b: Cycle<0>,
@@ -130,13 +121,10 @@ suite('Encode human readable types', () => {
// b: Cycle<1>
// }
const a = makeStructType('A',
['a', 'b'],
[
makeCycleType(0),
makeCycleType(1),
]
);
const a = makeStructType('A', {
a: makeCycleType(0),
b: makeCycleType(1),
});
assertWriteType(`struct A {
a: Cycle<0>,

View File

@@ -539,7 +539,7 @@ suite('Encoding', () => {
});
test('list of union with type', () => {
const structType = makeStructType('S', ['x'], [numberType]);
const structType = makeStructType('S', {x: numberType});
assertEncoding([
uint8(ListKind), uint8(UnionKind), uint32(2) /* len */, uint8(BoolKind), uint8(TypeKind), false,
@@ -567,13 +567,10 @@ suite('Encoding', () => {
});
test('recursive struct', () => {
const structType = makeStructType('A6',
['cs', 'v'],
[
makeListType(makeCycleType(0)),
numberType,
]
);
const structType = makeStructType('A6', {
cs: makeListType(makeCycleType(0)),
v: numberType,
});
assertEncoding([
uint8(StructKind), 'A6', uint32(2) /* len */, 'cs', uint8(ListKind), uint8(CycleKind), uint32(0), 'v', uint8(NumberKind),

View File

@@ -84,13 +84,10 @@ suite('Struct', () => {
});
test('createStructClass', () => {
const typeA = makeStructType('A',
['b', 'c'],
[
numberType,
stringType,
]
);
const typeA = makeStructType('A', {
b: numberType,
c: stringType,
});
const A = createStructClass(typeA);
const a = new A({b: 1, c: 'hi'});
assert.instanceOf(a, Struct);
@@ -100,13 +97,10 @@ suite('Struct', () => {
});
test('type validation', () => {
const type = makeStructType('S1',
['o', 'x'],
[
stringType,
boolType,
]
);
const type = makeStructType('S1', {
o: stringType,
x: boolType,
});
assert.throws(() => {
newStructWithType(type, ['hi', 1]);
@@ -123,13 +117,10 @@ suite('Struct', () => {
// b: Bool
// l: List<S>
// }
const type = makeStructType('S',
['b', 'l'],
[
boolType,
makeListType(makeCycleType(0)),
]
);
const type = makeStructType('S', {
b: boolType,
l: makeListType(makeCycleType(0)),
});
const emptyList = new List([]);
newStructWithType(type, [true, emptyList]);
@@ -218,7 +209,11 @@ suite('Struct', () => {
assert.notEqual(s.hash, 'hash');
assert.isTrue(equals(s.type,
makeStructType('', ['chunks', 'hash', 'type'], [stringType, stringType, stringType])));
makeStructType('', {
chunks: stringType,
hash: stringType,
type: stringType,
})));
assert.deepEqual(s.chunks, []);
assert.instanceOf(s.hash, Hash);
});

View File

@@ -264,13 +264,11 @@ export function newStructWithValues<T: Struct>(type: Type<any>, values: Value[])
}
function computeTypeForStruct(name: string, data: StructData): Type<StructDesc> {
const fieldNames = Object.keys(data);
const fieldTypes = new Array(fieldNames.length);
fieldNames.sort();
for (let i = 0; i < fieldNames.length; i++) {
fieldTypes[i] = getTypeOfValue(data[fieldNames[i]]);
const fields = {};
for (const k in data) {
fields[k] = getTypeOfValue(data[k]);
}
return makeStructType(name, fieldNames, fieldTypes);
return makeStructType(name, fields);
}
/**

View File

@@ -82,14 +82,14 @@ suite('TypeCache', () => {
});
test('struct', () => {
const st = makeStructType('Foo',
['bar', 'foo'],
[stringType, numberType]
);
const st2 = makeStructType('Foo',
['bar', 'foo'],
[stringType, numberType]
);
const st = makeStructType('Foo', {
bar: stringType,
foo: numberType,
});
const st2 = makeStructType('Foo', {
bar: stringType,
foo: numberType,
});
assert.strictEqual(st, st2);
});
@@ -110,19 +110,15 @@ suite('TypeCache', () => {
});
test('Cyclic Struct', () => {
const st = makeStructType('Foo',
['foo'],
[
makeRefType(makeCycleType(0)),
]);
const st = makeStructType('Foo', {
foo: makeRefType(makeCycleType(0)),
});
assert.isFalse(st.hasUnresolvedCycle([]));
assert.strictEqual(st, st.desc.fields[0].type.desc.elemTypes[0]);
const st2 = makeStructType('Foo',
['foo'],
[
makeRefType(makeCycleType(0)),
]);
const st2 = makeStructType('Foo', {
foo: makeRefType(makeCycleType(0)),
});
assert.isFalse(st2.hasUnresolvedCycle([]));
assert.strictEqual(st, st2);
});
@@ -130,7 +126,9 @@ suite('TypeCache', () => {
test('Cyclic Unions', () => {
const ut = makeUnionType([makeCycleType(0), numberType, stringType, boolType, blobType,
valueType, typeType]);
const st = makeStructType('Foo', ['foo'], [ut]);
const st = makeStructType('Foo', {
foo: ut,
});
assert.strictEqual(ut.desc.elemTypes[0].kind, Kind.Cycle);
assert.strictEqual(st, st.desc.fields[0].type.desc.elemTypes[1]);
@@ -140,7 +138,9 @@ suite('TypeCache', () => {
// types.
const ut2 = makeUnionType([numberType, stringType, boolType, blobType, valueType, typeType,
makeCycleType(0)]);
const st2 = makeStructType('Foo', ['foo'], [ut]);
const st2 = makeStructType('Foo', {
foo: ut,
});
assert.strictEqual(ut2.desc.elemTypes[0].kind, Kind.Cycle);
assert.strictEqual(st2, st2.desc.fields[0].type.desc.elemTypes[1]);
@@ -152,8 +152,12 @@ suite('TypeCache', () => {
test('Invalid Cycles and Unions', () => {
assert.throws(() => {
makeStructType('A', ['a'], [makeStructType('A', ['a'], [makeCycleType(1)])]);
});
makeStructType('A', {
a: makeStructType('A', {
a: makeCycleType(1),
}),
});
}, 'unrolled cycle types are not supported; ahl owes you a beer');
});
test('Invalid Crazy Cycles and Unions', () => {
@@ -171,9 +175,13 @@ suite('TypeCache', () => {
* }
*/
assert.throws(() => {
makeStructType('A', ['a'], [makeUnionType(
[makeCycleType(0), makeStructType('A', ['a'], [makeCycleType(0), makeCycleType(1)])]
)]);
makeStructType('A', {
a: makeUnionType([
makeCycleType(0), makeStructType('A', {
a: makeUnionType([makeCycleType(0), makeCycleType(1)]),
}),
]),
});
});
});
});

View File

@@ -86,10 +86,18 @@ export default class TypeCache {
return notNull(trie.t);
}
makeStructType(name: string, fieldNames: string[], fieldTypes: Type<any>[]): Type<StructDesc> {
makeStructType(name: string, fields: { [key: string]: Type<any> }): Type<StructDesc> {
const fieldNames = Object.keys(fields).sort();
const fieldTypes = fieldNames.map(n => fields[n]);
return this.makeStructTypeQuickly(name, fieldNames, fieldTypes);
}
makeStructTypeQuickly(name: string, fieldNames: Array<string>, fieldTypes: Array<Type<any>>):
Type<StructDesc> {
if (fieldNames.length !== fieldTypes.length) {
throw new Error('Field names and types must be of equal length');
}
verifyStructName(name);
verifyFieldNames(fieldNames);

View File

@@ -31,13 +31,10 @@ suite('Type', () => {
const mapType = makeMapType(stringType, numberType);
const setType = makeSetType(stringType);
const mahType = makeStructType('MahStruct',
['Field1', 'Field2'],
[
stringType,
boolType,
]
);
const mahType = makeStructType('MahStruct', {
Field1: stringType,
Field2: boolType,
});
const mapRef = db.writeValue(mapType).targetHash;
const setRef = db.writeValue(setType).targetHash;
const mahRef = db.writeValue(mahType).targetHash;
@@ -83,7 +80,7 @@ suite('Type', () => {
test('verify struct field name', () => {
function assertInvalid(n: string) {
assert.throw(() => {
makeStructType('S', [n], [stringType]);
makeStructType('S', {[n]: stringType});
});
}
assertInvalid('');
@@ -97,7 +94,7 @@ suite('Type', () => {
assertInvalid('💩');
function assertValid(n: string) {
makeStructType('S', [n], [stringType]);
makeStructType('S', {[n]: stringType});
}
assertValid('a');
assertValid('A');
@@ -109,7 +106,7 @@ suite('Type', () => {
test('verify struct name', () => {
function assertInvalid(n: string) {
assert.throw(() => {
makeStructType(n, [], []);
makeStructType(n, {});
});
}
assertInvalid(' ');
@@ -122,7 +119,7 @@ suite('Type', () => {
assertInvalid('💩');
function assertValid(n: string) {
makeStructType(n, [], []);
makeStructType(n, {});
}
assertValid('');
assertValid('a');
@@ -145,16 +142,24 @@ suite('Type', () => {
});
test('union with cycle', () => {
const inodeType = makeStructType('Inode', ['attr', 'contents'], [
makeStructType('Attr', ['ctime', 'mode', 'mtime'], [numberType, numberType, numberType]),
makeUnionType([
makeStructType('Directory', ['entries'], [
makeMapType(stringType, makeCycleType(1)),
]),
makeStructType('File', ['data'], [blobType]),
makeStructType('Symlink', ['targetPath'], [stringType]),
const inodeType = makeStructType('Inode', {
attr: makeStructType('Attr', {
ctime: numberType,
mode: numberType,
mtime: numberType,
}),
contents: makeUnionType([
makeStructType('Directory', {
entries: makeMapType(stringType, makeCycleType(1)),
}),
makeStructType('File', {
data: blobType,
}),
makeStructType('Symlink', {
targetPath: stringType,
}),
]),
]);
});
const vr: any = null;
const t1 = notNull(inodeType.desc.getField('contents'));

View File

@@ -195,9 +195,9 @@ export function makeRefType(elemType: Type<any>): Type<CompoundDesc> {
return staticTypeCache.getCompoundType(Kind.Ref, elemType);
}
export function makeStructType(name: string, fieldNames: string[], fieldTypes: Type<any>[]):
export function makeStructType(name: string, fields: {[key: string]: Type<any>}) :
Type<StructDesc> {
return staticTypeCache.makeStructType(name, fieldNames, fieldTypes);
return staticTypeCache.makeStructType(name, fields);
}
/**

View File

@@ -232,6 +232,6 @@ export default class ValueDecoder {
fieldNames[i] = this._r.readString();
fieldTypes[i] = this.readType();
}
return this._tc.makeStructType(name, fieldNames, fieldTypes);
return this._tc.makeStructTypeQuickly(name, fieldNames, fieldTypes);
}
}

View File

@@ -109,14 +109,11 @@ suite('walk', () => {
});
test('struct', async () => {
const t = makeStructType('Thing',
['foo', 'list', 'num'],
[
stringType,
makeListType(numberType),
numberType,
]
);
const t = makeStructType('Thing', {
foo: stringType,
list: makeListType(numberType),
num: numberType,
});
const c = createStructClass(t);
const val = new c({

View File

@@ -139,14 +139,11 @@ function createNumber(i: number): Value {
return i;
}
const structType = makeStructType('S1',
['bool', 'num', 'str'],
[
boolType,
numberType,
stringType,
]
);
const structType = makeStructType('S1', {
bool: boolType,
num: numberType,
str: stringType,
});
function createStruct(i: number): Value {
return newStructWithType(structType,

View File

@@ -31,40 +31,25 @@ main().catch(ex => {
process.exit(1);
});
const imageType = makeStructType('',
['height', 'source', 'width'],
[
numberType,
stringType,
numberType,
]
);
const imageType = makeStructType('', {
height: numberType,
width: numberType,
source: stringType,
});
const photoType = makeStructType('',
['id', 'images'],
[
stringType,
makeListType(imageType),
]
);
const photoType = makeStructType('', {
id: stringType,
images: makeListType(imageType),
});
const placeType = makeStructType('',
['place'],
[
makeStructType('',
['location'],
[
makeStructType('',
['latitude', 'longitude'],
[
numberType,
numberType,
]
),
]
),
]
);
const placeType = makeStructType('', {
place: makeStructType('', {
location: makeStructType('', {
latitude: numberType,
longitude: numberType,
}),
}),
});
async function main(): Promise<void> {
const inSpec = DatasetSpec.parse(args._[0]);

View File

@@ -30,15 +30,11 @@ const args = argv
const sizes = ['t', 's', 'm', 'l', 'o'];
const flickrNum = makeUnionType([stringType, numberType]);
const sizeTypes = sizes.map(s =>
makeStructType('',
['height_' + s, 'url_' + s, 'width_' + s],
[
flickrNum,
stringType,
flickrNum,
]
)
);
makeStructType('', {
['height_' + s]: flickrNum,
['width_' + s]: flickrNum,
['url_' + s]: stringType,
}));
// This is effectively:
// union {
@@ -68,11 +64,7 @@ const imageType = makeUnionType(sizeTypes.map(st => {
newFields[name] = type;
});
const fieldNames = Object.keys(newFields);
fieldNames.sort();
const fieldTypes = fieldNames.map(fn => newFields[fn]);
return makeStructType('', fieldNames, fieldTypes);
return makeStructType('', newFields);
}));
const nsInSecond = 10e9;

View File

@@ -43,12 +43,12 @@ const args = argv
// entries: Map<String, Cycle<0> | File>,
// }
const fileType = makeStructType('File', ['content'], [makeRefType(blobType)]);
const directoryType = makeStructType('Directory',
['entries'],
[
makeMapType(stringType, makeUnionType([fileType, makeCycleType(0)])),
]);
const fileType = makeStructType('File', {
content: makeRefType(blobType),
});
const directoryType = makeStructType('Directory', {
entries: makeMapType(stringType, makeUnionType([fileType, makeCycleType(0)])),
});
const File = createStructClass(fileType);
const Directory = createStructClass(directoryType);