Move insufficient-information checking to read-time

This commit is contained in:
Tony Garnock-Jones 2021-05-21 15:49:06 +02:00
parent ecdb314366
commit 20b676df27
11 changed files with 235 additions and 137 deletions

View File

@ -1,6 +1,6 @@
{
"name": "@preserves/schema",
"version": "0.9.0",
"version": "0.11.0",
"description": "Schema support for Preserves data serialization format",
"homepage": "https://gitlab.com/preserves/preserves",
"license": "Apache-2.0",

View File

@ -0,0 +1,128 @@
import { typeFor } from './gentype';
import { ANY_TYPE } from './type';
import * as M from './meta';
export function checkSchema(schema: M.Schema): M.Schema {
const checker = new Checker();
schema.definitions.forEach(checker.checkDefinition.bind(checker));
if (checker.problems.length > 0) {
throw new Error(`Cannot produce unconverter: insufficient information in:\n` +
checker.problems.map(c => ' - ' + c).join('\n'));
}
return schema;
}
class Checker {
problems: Array<string> = [];
recordProblem(context: string, detail: string): void {
this.problems.push(`${detail} in ${context}`);
}
checkBinding(scope: Set<string>, sym: symbol, context: string): void {
const name = sym.description!;
if (scope.has(name)) {
this.recordProblem(context, `duplicate binding named ${JSON.stringify(name)}`);
} else {
scope.add(name);
}
}
checkDefinition(def: M.Definition, name: symbol): void {
switch (def._variant) {
case 'or':
[def.pattern0, def.pattern1, ... def.patternN].forEach(({ variantLabel, pattern }) =>
this.checkPattern(new Set(), pattern, `variant ${variantLabel} of ${name.description!}`));
break;
case 'and': {
const scope = new Set<string>();
[def.pattern0, def.pattern1, ... def.patternN].forEach((p) =>
this.checkNamedPattern(scope, p, name.description!));
break;
}
case 'Pattern':
this.checkPattern(new Set(), def.value, name.description!);
break;
}
}
checkNamedPattern(scope: Set<string>, p: M.NamedPattern, context: string): void {
switch (p._variant) {
case 'named':
this.checkPattern(scope,
M.Pattern.SimplePattern(p.value.pattern),
`${p.value.name.description!} of ${context}`);
break;
case 'anonymous':
this.checkPattern(scope, p.value, context);
break;
}
}
checkPattern(scope: Set<string>, p: M.Pattern, context: string): void {
const t = typeFor((_ref) => ANY_TYPE, p);
switch (p._variant) {
case 'SimplePattern':
if (p.value._variant !== 'lit' && (t.kind === 'record' || t.kind === 'unit')) {
this.recordProblem(context, 'cannot recover serialization of non-literal pattern');
}
break;
case 'CompoundPattern':
((p: M.CompoundPattern): void => {
switch (p._variant) {
case 'rec':
this.checkNamedPattern(scope, p.label, `label of ${context}`);
this.checkNamedPattern(scope, p.fields, `fields of ${context}`);
break;
case 'tuple':
p.patterns.forEach((pp, i) =>
this.checkNamedPattern(scope, pp, `item ${i} of ${context}`));
break;
case 'tuple*':
if (p.variable._variant === 'named') {
this.checkBinding(scope, p.variable.value.name, context);
this.checkPattern(scope,
M.Pattern.SimplePattern(p.variable.value.pattern),
`${JSON.stringify(p.variable.value.name.description!)} of ${context}`);
} else {
if (t.kind !== 'array') {
this.recordProblem(context, 'unable to reconstruct tail of tuple* pattern');
}
this.checkPattern(scope,
M.Pattern.SimplePattern(p.variable.value),
`variable-length portion of ${context}`);
}
p.fixed.forEach((pp, i) =>
this.checkNamedPattern(scope, pp, `item ${i} of ${context}`));
break;
case 'setof':
if (t.kind !== 'set') {
this.recordProblem(context, 'unable to reconstruct set');
}
this.checkPattern(scope,
M.Pattern.SimplePattern(p.pattern),
`set in ${context}`);
break;
case 'dictof':
if (t.kind !== 'dictionary') {
this.recordProblem(context, 'unable to reconstruct dictionary');
}
this.checkPattern(scope,
M.Pattern.SimplePattern(p.key),
`key in dictionary in ${context}`);
this.checkPattern(scope,
M.Pattern.SimplePattern(p.value),
`value in dictionary in ${context}`);
break;
case 'dict':
p.entries.forEach((np, key) =>
this.checkNamedPattern(
scope,
M.promoteNamedSimplePattern(M.addNameIfAbsent(np, key)),
`entry ${key.asPreservesText()} in dictionary in ${context}`));
break;
}
})(p.value);
}
}
}

View File

@ -2,9 +2,10 @@ import { stringify } from "@preserves/core";
import * as M from "./meta";
import { CompilerOptions, ModuleContext } from "./compiler/context";
import { Formatter, block, seq } from "./compiler/block";
import { typeForDefinition } from "./compiler/gentype";
import { typeForDefinition } from "./gentype";
import { converterForDefinition } from "./compiler/genconverter";
import { renderType, Type } from "./compiler/type";
import { Type } from "./type";
import { renderType } from "./compiler/rendertype";
import { genConstructor } from "./compiler/genctor";
import { unconverterForDefinition } from "./compiler/genunconverter";
import { sourceCodeFor } from "./compiler/value";
@ -16,12 +17,12 @@ export function compile(env: M.Environment, schema: M.Schema, options: CompilerO
mod.defineType(seq(`export type _embedded = `,
renderType(embeddedName._variant === 'false'
? Type.ref('any')
: typeForDefinition(mod, M.Definition.Pattern(M.Pattern.SimplePattern(M.SimplePattern.Ref(embeddedName.value))))),
: typeForDefinition(mod.resolver(), M.Definition.Pattern(M.Pattern.SimplePattern(M.SimplePattern.Ref(embeddedName.value))))),
`;`));
mod.defineType(`export type _val = _.Value<_embedded>;`);
for (const [name, def] of schema.definitions) {
const t = typeForDefinition(mod, def);
const t = typeForDefinition(mod.resolver(), def);
const nameStr = stringify(name);
mod.defineType(seq(`export type ${nameStr} = `, renderType(t), `;`));
@ -60,7 +61,7 @@ export function compile(env: M.Environment, schema: M.Schema, options: CompilerO
mod.defineFunction(ctx =>
seq(`export function from${name.description!}`,
'(_v: ', name.description!, '): _val ',
ctx.block(() => unconverterForDefinition(ctx, name.description!, def, '_v'))));
ctx.block(() => unconverterForDefinition(ctx, def, '_v'))));
}
const f = new Formatter();

View File

@ -2,7 +2,8 @@ import { Dictionary, KeyedSet, Position } from "@preserves/core";
import { refPosition } from "../reader";
import * as M from "../meta";
import { block, braces, commas, formatItems, Item, keyvalue, seq } from "./block";
import { ANY_TYPE, renderType, Type, variantInitFor } from "./type";
import { ANY_TYPE, AtomicType, Type } from "../type";
import { renderType, variantInitFor } from "./rendertype";
export interface CompilerOptions {
preservesModule?: string;
@ -67,6 +68,15 @@ export class ModuleContext {
defineFunction(f: (ctx: FunctionContext) => Item): void {
this.functiondefs.push(f(new FunctionContext(this)));
}
resolver(): (ref: M.Ref) => AtomicType {
return (ref) => M.lookup(refPosition(ref), ref, this.env,
(_p) => Type.ref(ref.name.description!),
(modId, modPath,_p) => {
this.imports.add([modId, modPath]);
return Type.ref(`${modId}.${ref.name.description!}`);
});
}
}
export class FunctionContext {

View File

@ -1,9 +1,9 @@
import { FunctionContext } from "./context";
import * as M from '../meta';
import { block, Item, seq } from "./block";
import { simpleType, dictionaryType, setType, typeFor } from "./gentype";
import { simpleType, dictionaryType, setType, typeFor } from "../gentype";
import { refPosition } from "../reader";
import { ANY_TYPE, Type } from "./type";
import { ANY_TYPE, Type } from "../type";
export function converterForDefinition(
ctx: FunctionContext,
@ -48,7 +48,7 @@ function converterForPattern(
if (simpleValue === void 0) {
return [ctx.buildCapturedCompound(dest)];
} else if (ctx.variantName !== void 0) {
if (typeFor(ctx.mod, p).kind === 'unit') {
if (typeFor(ctx.mod.resolver(), p).kind === 'unit') {
return [ctx.buildCapturedCompound(dest)];
} else {
return [ctx.withCapture('value',
@ -98,7 +98,7 @@ function converterForArray(ctx: FunctionContext,
k: (dest: string) => Item[]): Item
{
const postCheck = () => {
const r = ctx.gentemp(Type.array(simpleType(ctx.mod, M.unnameSimplePattern(arrayType))));
const r = ctx.gentemp(Type.array(simpleType(ctx.mod.resolver(), M.unnameSimplePattern(arrayType))));
const v = ctx.gentempname();
return [
seq(`${r} = []`),
@ -125,14 +125,14 @@ function converterFor(
let maybeName = M.nameFor(np);
if (p._variant === 'SimplePattern') {
const dest = ctx.gentemp(simpleType(ctx.mod, p.value));
const dest = ctx.gentemp(simpleType(ctx.mod.resolver(), p.value));
return [... converterForSimple(ctx, p.value, src, dest),
ctx.convertCapture(maybeName, dest, ks)];
} else {
switch (p.value._variant) {
case 'setof': {
const setPattern = p.value.pattern;
const r = ctx.gentemp(setType(ctx.mod, setPattern));
const r = ctx.gentemp(setType(ctx.mod.resolver(), setPattern));
const v = ctx.gentempname();
return [
seq(`if (_.Set.isSet<_embedded>(${src})) `, ctx.block(() => [
@ -147,7 +147,7 @@ function converterFor(
case 'dictof': {
const keyPattern = p.value.key;
const valPattern = p.value.value;
const r = ctx.gentemp(dictionaryType(ctx.mod, keyPattern, valPattern));
const r = ctx.gentemp(dictionaryType(ctx.mod.resolver(), keyPattern, valPattern));
const v = ctx.gentempname();
const k = ctx.gentempname();
return [

View File

@ -1,6 +1,7 @@
import * as M from '../meta';
import { block, braces, Item, keyvalue, parens, seq } from "./block";
import { FieldType, renderType, SimpleType } from "./type";
import { FieldType, SimpleType } from "../type";
import { renderType } from "./rendertype";
export function genConstructor(
name: string,

View File

@ -2,12 +2,12 @@ import { refPosition } from '../reader';
import * as M from '../meta';
import { block, brackets, formatItems, Item, parens, seq } from './block';
import { FunctionContext } from "./context";
import { FieldType, renderType, SimpleType } from './type';
import { typeFor, typeForIntersection } from './gentype';
import { FieldType, SimpleType } from '../type';
import { typeFor, typeForIntersection } from '../gentype';
import { renderType } from "./rendertype";
export function unconverterForDefinition(
ctx: FunctionContext,
name: string,
def: M.Definition,
src: string): Item[]
{
@ -18,53 +18,30 @@ export function unconverterForDefinition(
seq(`case `, JSON.stringify(p.variantLabel), `: `, ctx.block(() => {
const hasValueField = p.pattern._variant === 'SimplePattern';
return [seq(`return `, unconverterForPattern(
ctx, name, p.pattern, hasValueField ? `${src}.value` : src))];
ctx, p.pattern, hasValueField ? `${src}.value` : src))];
})))))];
case 'and': {
const ps = [def.pattern0, def.pattern1, ... def.patternN];
const t = typeForIntersection(ctx.mod, ps);
const errs: [string, InsufficientInformationError][] = [];
const t = typeForIntersection(ctx.mod.resolver(), ps);
const cs = ps.flatMap(p => {
if (p._variant === 'anonymous' && p.value._variant === 'SimplePattern') {
return [];
} else {
try {
return [unconverterForNamed(ctx, p, src, t)];
} catch (e) {
if (e instanceof InsufficientInformationError) {
errs.push([name + '/' + (M.nameFor(p) ?? '<anonymous>'), e]);
return [];
}
throw e;
}
return [unconverterForNamed(ctx, p, src, t)];
}
});
if (cs.length === 0 || errs.length > 0) {
throw new Error(`Cannot produce unconverter for ${name}: ` +
errs.map(e => `${e[0]}: ${e[1].message}`).join(', '));
}
return [seq(`return `, (cs.length === 1)
? cs[0]
: seq(`_.merge`, parens(`(a, b) => (a === b) ? a : void 0`, ... cs)))];
}
case 'Pattern':
return [seq(`return `, unconverterForPattern(ctx, name, def.value, `${src}`))];
return [seq(`return `, unconverterForPattern(ctx, def.value, `${src}`))];
}
}
export class InsufficientInformationError extends Error {}
function unconverterForPattern(ctx: FunctionContext, name: string, a: M.Pattern, src: string): Item
function unconverterForPattern(ctx: FunctionContext, a: M.Pattern, src: string): Item
{
const t = typeFor(ctx.mod, a);
try {
return unconverterFor(ctx, a, src, t);
} catch (e) {
if (e instanceof InsufficientInformationError) {
throw new Error(`Cannot produce unconverter for ${name}: ${e.message}`);
}
throw e;
}
return unconverterFor(ctx, a, src, typeFor(ctx.mod.resolver(), a));
}
function stepSource(
@ -74,8 +51,7 @@ function stepSource(
{
if (t.kind !== 'record' || !t.fields.has(key)) {
throw new Error(
`Internal error: attempt to step type ` +
`${formatItems([renderType(t)])} with key ${key}`);
`Internal error: attempt to step type ${JSON.stringify(t)} with key ${key}`);
}
return {
steppedSrc: `${src}[${JSON.stringify(key)}]`,
@ -87,9 +63,6 @@ function unconverterFor(ctx: FunctionContext, p: M.Pattern, src: string, t: Simp
switch (p._variant) {
case 'SimplePattern':
return ((p: M.SimplePattern) => {
if (p._variant !== 'lit' && (t.kind === 'record' || t.kind === 'unit')) {
throw new InsufficientInformationError(`A Cannot produce unconverter TODO ${src} ${formatItems([renderType(t)])}`);
}
switch (p._variant) {
case 'any':
return `${src}`;
@ -141,10 +114,7 @@ function unconverterFor(ctx: FunctionContext, p: M.Pattern, src: string, t: Simp
`v`,
steppedType.type))));
} else {
if (t.kind !== 'array') {
throw new InsufficientInformationError(
`B Cannot produce unconverter TODO ${src} ${formatItems([renderType(t)])}`);
}
if (t.kind !== 'array') throw new Error("Internal error");
varexp = seq(src, `.map`, parens(
seq(`v => `, unconverterFor(
ctx,
@ -161,19 +131,11 @@ function unconverterFor(ctx: FunctionContext, p: M.Pattern, src: string, t: Simp
}
}
case 'setof':
if (t.kind !== 'set') {
throw new InsufficientInformationError(
`C Cannot produce unconverter TODO ${src} ${formatItems([renderType(t)])}`);
}
return seq(`new _.Set<_embedded>`, parens(
`_.Array.from(${src}.values()).map(v => `,
unconverterFor(ctx, M.Pattern.SimplePattern(p.pattern), 'v', t),
`)`));
case 'dictof':
if (t.kind !== 'dictionary') {
throw new InsufficientInformationError(
`D Cannot produce unconverter TODO ${src} ${formatItems([renderType(t)])}`);
}
return seq(`new _.Dictionary<_embedded>`, parens(seq(
`_.Array.from(${src}.entries()).map(([k, v]) => `,
brackets(

View File

@ -1,41 +1,6 @@
import { SimpleType, Type } from "../type";
import { anglebrackets, braces, Item, keyvalue, opseq, seq } from "./block";
export type Type =
| { kind: 'union', variants: VariantMap } // zero: never
| SimpleType
export type SimpleType = AtomicType | CompoundType
export type FieldType = AtomicType | CollectionType;
export type AtomicType =
| { kind: 'unit' }
| { kind: 'ref', typeName: string } // also for base types
export type CompoundType =
| CollectionType
| { kind: 'record', fields: FieldMap }
export type CollectionType =
| { kind: 'array', type: AtomicType }
| { kind: 'set', type: AtomicType }
| { kind: 'dictionary', key: AtomicType, value: AtomicType }
export type VariantMap = Map<string, SimpleType>;
export type FieldMap = Map<string, FieldType>;
export namespace Type {
export const union = (variants: VariantMap): Type => ({ kind: 'union', variants });
export const unit = (): AtomicType => ({ kind: 'unit' });
export const ref = (typeName: string): AtomicType => ({ kind: 'ref', typeName });
export const record = (fields: FieldMap): CompoundType => ({ kind: 'record', fields });
export const array = (type: AtomicType): CollectionType => ({ kind: 'array', type });
export const set = (type: AtomicType): CollectionType => ({ kind: 'set', type });
export const dictionary = (key: AtomicType, value: AtomicType): CollectionType => (
{ kind: 'dictionary', key, value });
}
export const ANY_TYPE: AtomicType = Type.ref('_val');
export function variantInitFor(variantName: string | undefined) : Item[] {
return variantName === void 0 ? [] : [variantFor(variantName)];
}

View File

@ -1,62 +1,62 @@
import { refPosition } from "../reader";
import * as M from "../meta";
import { ModuleContext } from "./context";
import * as M from "./meta";
import { ANY_TYPE, AtomicType, CollectionType, FieldMap, SimpleType, Type } from "./type";
export function typeForDefinition(mod: ModuleContext, d: M.Definition): Type {
export type RefResolver = (ref: M.Ref) => AtomicType;
export function typeForDefinition(resolver: RefResolver, d: M.Definition): Type {
switch (d._variant) {
case 'or':
return Type.union(
new Map([d.pattern0, d.pattern1, ... d.patternN].map(a =>
[a.variantLabel, typeFor(mod, a.pattern)])));
[a.variantLabel, typeFor(resolver, a.pattern)])));
case 'and':
return typeForIntersection(mod, [d.pattern0, d.pattern1, ... d.patternN]);
return typeForIntersection(resolver, [d.pattern0, d.pattern1, ... d.patternN]);
case 'Pattern':
return typeFor(mod, d.value);
return typeFor(resolver, d.value);
}
}
export function typeForIntersection(mod: ModuleContext, ps: M.NamedPattern[]): SimpleType {
export function typeForIntersection(resolver: RefResolver, ps: M.NamedPattern[]): SimpleType {
const fs = new Map();
ps.forEach(p => gatherFields(fs, mod, p));
ps.forEach(p => gatherFields(fs, resolver, p));
return fs.size > 0 ? Type.record(fs) : Type.unit();
}
export function setType(mod: ModuleContext, p: M.SimplePattern): CollectionType {
return Type.set(simpleType(mod, p));
export function setType(resolver: RefResolver, p: M.SimplePattern): CollectionType {
return Type.set(simpleType(resolver, p));
}
export function dictionaryType(mod: ModuleContext,
export function dictionaryType(resolver: RefResolver,
kp: M.SimplePattern,
vp: M.SimplePattern): CollectionType
{
return Type.dictionary(simpleType(mod, kp), simpleType(mod, vp));
return Type.dictionary(simpleType(resolver, kp), simpleType(resolver, vp));
}
export function typeFor(mod: ModuleContext, p: M.Pattern): SimpleType {
export function typeFor(resolver: RefResolver, p: M.Pattern): SimpleType {
if (p._variant === 'SimplePattern') {
return simpleType(mod, p.value);
return simpleType(resolver, p.value);
} else {
switch (p.value._variant) {
case 'setof':
return setType(mod, p.value.pattern);
return setType(resolver, p.value.pattern);
case 'dictof':
return dictionaryType(mod, p.value.key, p.value.value);
return dictionaryType(resolver, p.value.key, p.value.value);
default: {
const arrayType = M.simpleArray(p.value);
if (arrayType === void 0) {
const fs = new Map();
compoundFields(fs, mod, p.value);
compoundFields(fs, resolver, p.value);
return fs.size > 0 ? Type.record(fs) : Type.unit();
} else {
return Type.array(simpleType(mod, arrayType));
return Type.array(simpleType(resolver, arrayType));
}
}
}
}
}
export function simpleType(mod: ModuleContext, p: M.SimplePattern): AtomicType {
export function simpleType(resolver: RefResolver, p: M.SimplePattern): AtomicType {
switch (p._variant) {
case 'any':
return ANY_TYPE;
@ -75,32 +75,27 @@ export function simpleType(mod: ModuleContext, p: M.SimplePattern): AtomicType {
case 'lit':
return Type.unit();
case 'Ref':
return M.lookup(refPosition(p.value), p.value, mod.env,
(_p) => Type.ref(p.value.name.description!),
(modId, modPath,_p) => {
mod.imports.add([modId, modPath]);
return Type.ref(`${modId}.${p.value.name.description!}`);
});
return resolver(p.value);
default:
((_p: never) => {})(p);
throw new Error("Unreachable");
}
}
function compoundFields(fs: FieldMap, mod: ModuleContext, p: M.CompoundPattern): void {
function compoundFields(fs: FieldMap, resolver: RefResolver, p: M.CompoundPattern): void {
switch (p._variant) {
case 'rec':
gatherFields(fs, mod, p.label);
gatherFields(fs, mod, p.fields);
gatherFields(fs, resolver, p.label);
gatherFields(fs, resolver, p.fields);
break;
case 'tuple':
p.patterns.forEach(pp => gatherFields(fs, mod, pp));
p.patterns.forEach(pp => gatherFields(fs, resolver, pp));
break;
case 'tuple*': {
p.fixed.forEach(pp => gatherFields(fs, mod, pp));
p.fixed.forEach(pp => gatherFields(fs, resolver, pp));
const n = p.variable;
if (n._variant === 'named') {
fs.set(n.value.name.description!, Type.array(simpleType(mod, n.value.pattern)));
fs.set(n.value.name.description!, Type.array(simpleType(resolver, n.value.pattern)));
}
break;
}
@ -109,7 +104,7 @@ function compoundFields(fs: FieldMap, mod: ModuleContext, p: M.CompoundPattern):
break;
case 'dict':
p.entries.forEach((n, k) =>
gatherFields(fs, mod, M.promoteNamedSimplePattern(M.addNameIfAbsent(n, k))));
gatherFields(fs, resolver, M.promoteNamedSimplePattern(M.addNameIfAbsent(n, k))));
break;
default:
((_p: never) => {})(p);
@ -117,10 +112,10 @@ function compoundFields(fs: FieldMap, mod: ModuleContext, p: M.CompoundPattern):
}
}
function gatherFields(fs: FieldMap, mod: ModuleContext, n: M.NamedPattern): void {
function gatherFields(fs: FieldMap, resolver: RefResolver, n: M.NamedPattern): void {
if (n._variant === 'named') {
fs.set(n.value.name.description!, simpleType(mod, n.value.pattern));
fs.set(n.value.name.description!, simpleType(resolver, n.value.pattern));
} else if (n.value._variant === 'CompoundPattern') {
compoundFields(fs, mod, n.value.value);
compoundFields(fs, resolver, n.value.value);
}
}

View File

@ -2,6 +2,7 @@ import { Reader, Annotated, Dictionary, is, peel, preserves, Record, strip, Tupl
import { Input, Pattern, Schema, Definition, CompoundPattern, SimplePattern } from './meta';
import * as M from './meta';
import { SchemaSyntaxError } from './error';
import { checkSchema } from './checker';
const positionTable = new WeakMap<object, Position>();
@ -58,7 +59,7 @@ function _readSchema(source: string, options?: ReaderOptions<never>): Array<Inpu
export function readSchema(source: string,
options?: ReaderOptions<never> & SchemaReaderOptions): Schema
{
return parseSchema(_readSchema(source, options), options ?? {});
return checkSchema(parseSchema(_readSchema(source, options), options ?? {}));
}
export function parseSchema(toplevelTokens: Array<Input>,

View File

@ -0,0 +1,35 @@
export type Type =
| { kind: 'union', variants: VariantMap } // zero: never
| SimpleType
export type SimpleType = AtomicType | CompoundType
export type FieldType = AtomicType | CollectionType;
export type AtomicType =
| { kind: 'unit' }
| { kind: 'ref', typeName: string } // also for base types
export type CompoundType =
| CollectionType
| { kind: 'record', fields: FieldMap }
export type CollectionType =
| { kind: 'array', type: AtomicType }
| { kind: 'set', type: AtomicType }
| { kind: 'dictionary', key: AtomicType, value: AtomicType }
export type VariantMap = Map<string, SimpleType>;
export type FieldMap = Map<string, FieldType>;
export namespace Type {
export const union = (variants: VariantMap): Type => ({ kind: 'union', variants });
export const unit = (): AtomicType => ({ kind: 'unit' });
export const ref = (typeName: string): AtomicType => ({ kind: 'ref', typeName });
export const record = (fields: FieldMap): CompoundType => ({ kind: 'record', fields });
export const array = (type: AtomicType): CollectionType => ({ kind: 'array', type });
export const set = (type: AtomicType): CollectionType => ({ kind: 'set', type });
export const dictionary = (key: AtomicType, value: AtomicType): CollectionType => (
{ kind: 'dictionary', key, value });
}
export const ANY_TYPE: AtomicType = Type.ref('_val');