diff --git a/src/collector/declarations.js b/src/collector/declarations.js index df22ccc..dbe2bcb 100644 --- a/src/collector/declarations.js +++ b/src/collector/declarations.js @@ -7,7 +7,7 @@ import type { Block, ClassDeclaration, ExportDefaultDeclaration, ExportNamedDeclaration, Identifier, ImportDeclaration, ImportDefaultSpecifier, ImportSpecifier, InterfaceDeclaration, Node, TypeAlias, TypeParameterDeclaration, VariableDeclaration, VariableDeclarator, - DeclareTypeAlias, DeclareInterface, DeclareClass, + DeclareTypeAlias, DeclareInterface, DeclareClass } from '@babel/types'; import { @@ -15,6 +15,7 @@ import { isExportNamedDeclaration, isIdentifier, isImportDeclaration, isImportNamespaceSpecifier, isImportSpecifier, isInterfaceDeclaration, isObjectPattern, isObjectProperty, isDeclareClass, isStringLiteral, isTypeAlias, isVariableDeclaration, isDeclareTypeAlias, isDeclareInterface, + isVariableDeclarator } from '@babel/types'; import {invariant} from '../utils'; @@ -106,7 +107,23 @@ function processVariableDeclaration(ctx: Context, node: VariableDeclaration) { } function processVariableDeclarator(ctx: Context, node: VariableDeclarator) { - const path = extractRequire(node.init); + if (isRequireExpression(node.init)) { + extractExternals(ctx, node); + } else if (isIdentifier(node.id)) { + extractIdentifier(ctx, node); + } +} + +function extractIdentifier(ctx: Context, node: VariableDeclarator) { + const {id} = node; + + invariant(isIdentifier(id)); + + ctx.declare(id.name, node); +} + +function extractExternals(ctx: Context, node: VariableDeclarator) { + const path = extractRequirePath(node.init); if (path == null) { return null; @@ -125,13 +142,23 @@ function processVariableDeclarator(ctx: Context, node: VariableDeclarator) { } } -function extractRequire(node: Node): ?string { +function extractRequirePath(node: Node): ?string { + if ( + // TODO: flow fails but should not + isRequireExpression(node) && isStringLiteral(node.arguments[0]) + ) { + return String(node.arguments[0].value); + } + + return null; +} + +function isRequireExpression(node: Node): boolean %checks { return isCallExpression(node) && isIdentifier(node.callee, {name: 'require'}) && node.arguments.length > 0 // TODO: warning about dynamic imports. && isStringLiteral(node.arguments[0]) - ? node.arguments[0].value : null; } function extractCommonjsDefaultExternal(node: Identifier, path: string): ExternalInfo { @@ -162,13 +189,29 @@ function extractCommonjsNamedExternals<+T: Node>(nodes: T[], path: string): Exte * * TODO: support "export from" form. * TODO: support commonjs. + * TODO: implement ObjectPattern, ArrayPattern, RestElement for variable declaration */ - function processExportNamedDeclaration(ctx: Context, node: ExportNamedDeclaration) { if (isDeclaration(node.declaration)) { const reference = processDeclaration(ctx, node.declaration); ctx.provide(reference, reference); + } else if (isVariableDeclaration(node.declaration)) { + const {declaration} = node; + processVariableDeclaration(ctx, declaration); + const {declarations} = declaration; + + if (Array.isArray(declarations)) { + for (const declarator of declarations) { + const {id} = declarator; + + if (!isVariableDeclarator(declarator) || !isIdentifier(id)) { + continue; + } + + ctx.provide(id.name, id.name); + } + } } for (const specifier of node.specifiers) { diff --git a/src/collector/definitions.js b/src/collector/definitions.js index c4dd86a..d335f4e 100644 --- a/src/collector/definitions.js +++ b/src/collector/definitions.js @@ -99,7 +99,7 @@ function processClassDeclaration(ctx: Context, node: ClassDeclaration) { ctx.define(name, intersection); } -function makeType(ctx: Context, node: FlowTypeAnnotation): ?Type { +export function makeType(ctx: Context, node: FlowTypeAnnotation): ?Type { // TODO: ThisTypeAnnotation // TODO: VoidTypeAnnotation // TODO: TypeofTypeAnnotation @@ -134,6 +134,12 @@ function makeType(ctx: Context, node: FlowTypeAnnotation): ?Type { return t.createAny(); case 'MixedTypeAnnotation': return t.createMixed(); + case 'TypeofTypeAnnotation': + if (!node.argument || node.argument.type !== 'GenericTypeAnnotation') { + return t.createAny(); + } + + return makeType(ctx, node.argument); case 'FunctionTypeAnnotation': return null; default: diff --git a/src/collector/globals.js b/src/collector/globals.js index 28ae19c..093a769 100644 --- a/src/collector/globals.js +++ b/src/collector/globals.js @@ -105,11 +105,11 @@ function unwrap(params: (?Type)[]): ?Type { function keys(params: (?Type)[], resolve: TypeId => Type): ?Type { invariant(params.length === 1); - const [ref] = params; - - invariant(ref && ref.kind === 'reference'); + const [param] = params; + invariant(param); + invariant(param.kind === 'reference' || param.kind === 'record'); - const record = resolve(ref.to); + const record = param.kind === 'reference' ? resolve(param.to) : param; invariant(record.kind === 'record'); @@ -127,11 +127,11 @@ function keys(params: (?Type)[], resolve: TypeId => Type): ?Type { function values(params: (?Type)[], resolve: TypeId => Type): ?Type { invariant(params.length === 1); - const [ref] = params; - - invariant(ref && ref.kind === 'reference'); + const [param] = params; + invariant(param); + invariant(param.kind === 'reference' || param.kind === 'record'); - const record = resolve(ref.to); + const record = param.kind === 'reference' ? resolve(param.to) : param; invariant(record.kind === 'record'); diff --git a/src/collector/index.js b/src/collector/index.js index e7ed6ae..0ab760f 100644 --- a/src/collector/index.js +++ b/src/collector/index.js @@ -10,6 +10,7 @@ import traverse from './traverse'; import globals from './globals'; import definitionGroup from './definitions'; import declarationGroup from './declarations'; +import variableGroup from './variables'; import Fund from '../fund'; import Module from './module'; import Scope from './scope'; @@ -19,7 +20,7 @@ import type Parser from '../parser'; import type {Type, TypeId} from '../types'; import type {TemplateParam} from './query'; -const VISITOR = Object.assign({}, definitionGroup, declarationGroup); +const VISITOR = Object.assign({}, definitionGroup, declarationGroup, variableGroup); export default class Collector { +root: string; diff --git a/src/collector/variables.js b/src/collector/variables.js new file mode 100644 index 0000000..f02d18f --- /dev/null +++ b/src/collector/variables.js @@ -0,0 +1,215 @@ +// @flow + +import type { + Identifier, FlowTypeAnnotation, + UpdateExpression, UnaryExpression, StringLiteral, SequenceExpression, + ObjectExpression, NumericLiteral, NullLiteral, NewExpression, + BooleanLiteral, ArrayExpression, VariableDeclarator, + ObjectProperty, SpreadProperty, SpreadElement +} from '@babel/types'; + +import type { + Type, RecordType, Field, ArrayType, TupleType, MapType, MaybeType +} from '../types'; + +import { + Node, + isUpdateExpression,isUnaryExpression, isStringLiteral, isSequenceExpression, + isObjectExpression, isNumericLiteral, isNullLiteral, isNewExpression, + isBooleanLiteral, isArrayExpression, isIdentifier, isTypeAnnotation, + isObjectProperty, isSpreadProperty, isSpreadElement +} from '@babel/types'; + +import * as t from '../types'; +import {invariant, uniqLastBy} from '../utils'; +import Context from './context'; +import {makeType as makeTypeFromTypeAnnotation} from './definitions'; + +// TODO MetaProperty, MemberExpression, LogicalExpression, FunctionExpression, +// ConditionalExpression, CallExpression, ArrowFunctionExpression +type AvailableExpression = + UpdateExpression | UnaryExpression | StringLiteral | SequenceExpression | + ObjectExpression | NumericLiteral | NullLiteral | NewExpression | + BooleanLiteral | ArrayExpression | Identifier; + +function processVariableDeclarator (ctx: Context, node: VariableDeclarator) { + const {id, init} = node; + + if (!isIdentifier(id)) { + return; + } + + if (isTypeAnnotation(id.typeAnnotation)) { + processTypeFromTypeAnnotation(ctx, id) + } else if (isAvailableValueExpression(init)) { + processTypeFromInit(ctx, id, init); + } +} + +function processTypeFromTypeAnnotation(ctx: Context, id: Identifier) { + invariant(id.typeAnnotation); + + const {name, typeAnnotation} = id; + const type = makeTypeFromTypeAnnotation(ctx, typeAnnotation); + + // TODO: support function variables. + invariant(type); + + ctx.define(name, type); +} + +function isAvailableValueExpression(value: Node): boolean %checks { + return isUpdateExpression(value) || isUnaryExpression(value) || isStringLiteral(value) || isSequenceExpression(value) || + isObjectExpression(value) || isNumericLiteral(value) || isNullLiteral(value) || isNewExpression(value) || + isBooleanLiteral(value) || isArrayExpression(value) || isIdentifier(value); +} + +function processTypeFromInit(ctx: Context, id: Identifier, init: AvailableExpression) { + const type = makeTypeFromValue(ctx, init); + invariant(type); + ctx.define(id.name, type); +} + +function makeTypeFromValue(ctx: Context, node: AvailableExpression) { + switch (node.type) { + // case init === null; + case 'Identifier': + if (node.name === 'undefined') { + // TODO Fails in json generator + // type = t.createLiteral(undefined); + return t.createAny(); + } else { + return makeReference(ctx, node.name); + } + case 'BooleanLiteral': + return t.createBoolean(); + case 'StringLiteral': + return t.createString(); + case 'NumericLiteral': + case 'UpdateExpression': + return t.createNumber('f64'); + case 'NullLiteral': + return t.createLiteral(null); + case 'ObjectExpression': + return createTypeFromObjectExpression(ctx, node); + case 'ArrayExpression': + return createTypeFromArrayExpression(ctx, node); + case 'SequenceExpression': + return createTypeFromSequenceExpression(ctx, node); + // TODO + case 'UnaryExpression': + case 'NewExpression': + default: + return t.createAny(); + } +} + +function createTypeFromObjectExpression(ctx: Context, value: ObjectExpression): ?RecordType { + const {properties} = value; + + const fields = properties + .reduce((acc, prop) => { + if (isObjectProperty(prop)) { + acc.push(makeFieldFromObjectProperty(ctx, prop)); + + return acc; + } + + if (isSpreadElement(prop) || isSpreadProperty(prop)) { + acc.push(...obtainFieldsFromSpreadProperty(ctx, prop)); + + return acc; + } + + return acc; + }, []) + .filter(Boolean); + + return t.createRecord(uniqLastBy(fields, f => f.name)) +} + +function makeFieldFromObjectProperty(ctx: Context, node: ObjectProperty): ?Field { + if (!(isStringLiteral(node.key) || isIdentifier(node.key) || isNumericLiteral(node.key))) { + return null; + } + + const name = isIdentifier(node.key) ? node.key.name : String(node.key.value); + const value = isAvailableValueExpression(node.value) ? makeTypeFromValue(ctx, node.value) : null; + + if (!value) { + return null; + } + + return { + name, + value, + required: true + }; +} + +function obtainFieldsFromSpreadProperty(ctx: Context, node: SpreadProperty | SpreadElement): Field[] { + if (isObjectExpression(node.argument)) { + const type = createTypeFromObjectExpression(ctx, node.argument); + + return type ? type.fields : []; + } + + if (isIdentifier(node.argument)) { + const type = ctx.query(node.argument.name); + invariant(type); + + // TODO: ArrayExpression + if (type.kind !== 'record') { + return []; + } + + // TODO improve typing of t.clone + // flowlint-next-line unclear-type:off + return ((t.clone(type): any): RecordType).fields; + } + + // TODO: ArrayExpression, SequenceExpression + return []; +} + +function createTypeFromArrayExpression(ctx: Context, node: ArrayExpression): ArrayType { + const types: Type[] = node.elements.map(element => { + if (!isAvailableValueExpression(element)) { + return null; + } + + return makeTypeFromValue(ctx, element); + }).filter(Boolean); + + return t.createArray(t.createUnion(types)); +} + +function createTypeFromSequenceExpression(ctx: Context, node: SequenceExpression): ?Type { + const {expressions} = node; + + if (!Array.isArray(expressions) || !expressions.length) { + return null; + } + + const last = expressions[expressions.length - 1]; + + return isAvailableValueExpression(last) ? makeTypeFromValue(ctx, last) : null; +} + +function makeReference(ctx: Context, name: string): ?Type { + const type = ctx.query(name); + + if (!type) { + return null; + } + + if (!type.id) { + return t.clone(type); + } + + return t.createReference(t.clone(type.id)); +} + +export default { + VariableDeclarator: processVariableDeclarator, +} diff --git a/src/parser.js b/src/parser.js index d270aab..772f2d6 100644 --- a/src/parser.js +++ b/src/parser.js @@ -14,7 +14,7 @@ export default class Parser { allowSuperOutsideMethod: true, sourceType: 'module', // TODO: review other plugins. - plugins: ['*', 'jsx', 'flow', 'classProperties'], + plugins: ['*', 'jsx', 'flow', 'classProperties', 'objectRestSpread'], }); } } diff --git a/src/utils.js b/src/utils.js index c907338..c5e131a 100644 --- a/src/utils.js +++ b/src/utils.js @@ -31,3 +31,12 @@ export function partition(iter: Iterable, pred: T => boolean): [T[], T[]] return [left, right]; } + +export function uniqLastBy(iter: T[], unaryFn: T => K): T[] { + const map = iter.reduce( + (acc, value) => acc.set(unaryFn(value), value), + new Map() + ); + + return [...map.values()]; +} diff --git a/tests/samples/keys/schema.json b/tests/samples/keys/schema.json index 06b010f..e427978 100644 --- a/tests/samples/keys/schema.json +++ b/tests/samples/keys/schema.json @@ -6,6 +6,13 @@ "properties": {"a": {"type": "string"}, "b": {"type": "boolean"}}, "required": ["a", "b"] }, - "keys::Y": {"enum": ["a", "b"]} + "keys::Y": {"enum": ["a", "b"]}, + "keys::Z": {"enum": ["a", "b"]}, + "keys::u": { + "type": "object", + "properties": {"a": {"type": "string"}, "b": {"type": "number"}}, + "required": ["a", "b"] + }, + "keys::U": {"enum": ["a", "b"]} } } diff --git a/tests/samples/keys/source.js b/tests/samples/keys/source.js index d3dd44d..274d1cb 100644 --- a/tests/samples/keys/source.js +++ b/tests/samples/keys/source.js @@ -5,4 +5,12 @@ type X = { type Y = $Keys; -export {Y}; +type Z = $Keys<{ + a: string, + b: boolean, +}>; + +const u = {a: '', b: 42}; +type U = $Keys + +export {Y, Z, U}; diff --git a/tests/samples/keys/types.yaml b/tests/samples/keys/types.yaml index bf451fc..8ad423c 100644 --- a/tests/samples/keys/types.yaml +++ b/tests/samples/keys/types.yaml @@ -12,3 +12,22 @@ - {kind: literal, value: a} - {kind: literal, value: b} id: [keys, Y] +- kind: union + variants: + - {kind: literal, value: a} + - {kind: literal, value: b} + id: [keys, Z] +- kind: record + fields: + - name: a + value: {kind: string} + required: true + - name: b + value: {kind: number, repr: f64} + required: true + id: [keys, u] +- kind: union + variants: + - {kind: literal, value: a} + - {kind: literal, value: b} + id: [keys, U] diff --git a/tests/samples/typeof/schema.json b/tests/samples/typeof/schema.json new file mode 100644 index 0000000..4ea83ae --- /dev/null +++ b/tests/samples/typeof/schema.json @@ -0,0 +1,30 @@ +{ + "$schema": "http://json-schema.org/draft-06/schema#", + "definitions": { + "typeof::object": { + "type": "object", + "properties": { + "foo": {"type": "number"}, + "bar": {"type": "string"}, + "baz": {"type": "boolean"} + }, + "required": ["foo", "bar"] + }, + "typeof::A": { + "type": "object", + "properties": { + "foo": {"type": "number"}, + "bar": {"type": "string"}, + "baz": {"type": "boolean"} + }, + "required": ["foo", "bar"] + }, + "typeof::object2": {"$ref": "#/definitions/typeof::A"}, + "typeof::TypeOfObject": { + "$ref": "#/definitions/typeof::object" + }, + "typeof::TypeOfObject2": { + "$ref": "#/definitions/typeof::object2" + } + } +} diff --git a/tests/samples/typeof/source.js b/tests/samples/typeof/source.js new file mode 100644 index 0000000..37bebb5 --- /dev/null +++ b/tests/samples/typeof/source.js @@ -0,0 +1,17 @@ +const object: { + foo: number, + bar: string, + baz?: boolean +} = {foo: 42, bar: ''} + +export type TypeOfObject = typeof object; + +type A = { + foo: number, + bar: string, + baz?: boolean +}; + +const object2: A = {foo: 42, bar: ''} + +export type TypeOfObject2 = typeof object2; diff --git a/tests/samples/typeof/types.yaml b/tests/samples/typeof/types.yaml new file mode 100644 index 0000000..f473b50 --- /dev/null +++ b/tests/samples/typeof/types.yaml @@ -0,0 +1,33 @@ +- kind: record + fields: + - name: foo + value: {kind: number, repr: f64} + required: true + - name: bar + value: {kind: string} + required: true + - name: baz + value: {kind: boolean} + required: false + id: [typeof, object] +- kind: reference + to: [typeof, object] + id: [typeof, TypeOfObject] +- kind: record + fields: + - name: foo + value: {kind: number, repr: f64} + required: true + - name: bar + value: {kind: string} + required: true + - name: baz + value: {kind: boolean} + required: false + id: [typeof, A] +- kind: reference + to: [typeof, A] + id: [typeof, object2] +- kind: reference + to: [typeof, object2] + id: [typeof, TypeOfObject2] diff --git a/tests/samples/values/schema.json b/tests/samples/values/schema.json index 02e47ae..46f88ed 100644 --- a/tests/samples/values/schema.json +++ b/tests/samples/values/schema.json @@ -6,6 +6,13 @@ "properties": {"a": {"type": "string"}, "b": {"type": "boolean"}}, "required": ["a", "b"] }, - "values::Y": {"anyOf": [{"type": "string"}, {"type": "boolean"}]} + "values::Y": {"anyOf": [{"type": "string"}, {"type": "boolean"}]}, + "values::Z": {"anyOf": [{"type": "string"}, {"type": "boolean"}]}, + "values::u": { + "type": "object", + "properties": {"a": {"type": "string"}, "b": {"type": "number"}}, + "required": ["a", "b"] + }, + "values::U": {"anyOf": [{"type": "string"}, {"type": "number"}]} } } diff --git a/tests/samples/values/source.js b/tests/samples/values/source.js index 05831c2..48829f6 100644 --- a/tests/samples/values/source.js +++ b/tests/samples/values/source.js @@ -5,4 +5,12 @@ type X = { type Y = $Values; -export {Y}; +type Z = $Values<{ + a: string, + b: boolean, +}>; + +const u = {a: '', b: 42}; +type U = $Values + +export {Y, Z, U}; diff --git a/tests/samples/values/types.yaml b/tests/samples/values/types.yaml index c4d30d8..af42d34 100644 --- a/tests/samples/values/types.yaml +++ b/tests/samples/values/types.yaml @@ -12,3 +12,22 @@ - {kind: string} - {kind: boolean} id: [values, Y] +- kind: union + variants: + - {kind: string} + - {kind: boolean} + id: [values, Z] +- kind: record + fields: + - name: a + value: {kind: string} + required: true + - name: b + value: {kind: number, repr: f64} + required: true + id: [values, u] +- kind: union + variants: + - {kind: string} + - {kind: number, repr: f64} + id: [values, U] diff --git a/tests/samples/variables/schema.json b/tests/samples/variables/schema.json new file mode 100644 index 0000000..daa3df9 --- /dev/null +++ b/tests/samples/variables/schema.json @@ -0,0 +1,91 @@ +{ + "$schema": "http://json-schema.org/draft-06/schema#", + "definitions": { + "variables::obj": { + "type": "object", + "properties": {"foo": {"type": "number"}}, + "required": ["foo"] + }, + "variables::O": { + "type": "object", + "properties": {"foo": {"type": "number"}}, + "required": ["foo"] + }, + "variables::obj2": {"$ref": "#/definitions/variables::O"}, + "variables::obj3": {"$ref": "#/definitions/variables::obj"}, + "variables::num": {"type": "number"}, + "variables::str": {"type": "string"}, + "variables::nil": {"type": "null"}, + "variables::empty": true, + "variables::obj4": { + "type": "object", + "properties": { + "num": {"type": "number"}, + "str": {"type": "string"}, + "literal-literal": {"type": "string"}, + "computed": {"type": "string"}, + "null": {"type": "null"}, + "true": {"type": "boolean"}, + "false": {"type": "boolean"}, + "42": {"type": "number"}, + "array": { + "type": "array", + "items": { + "anyOf": [ + {"type": "number"}, + {"type": "string"}, + {"type": "boolean"}, + {"enum": [null]} + ] + } + }, + "seq": {"type": "number"} + }, + "required": ["num", "str", "literal-literal", "computed", "42", "null", "true", "false", "array", "seq"] + }, + "variables::obj5": { + "type": "object", + "properties": {"obj": {"$ref": "#/definitions/variables::obj"}}, + "required": ["obj"] + }, + "variables::obj6": { + "type": "object", + "properties": { + "num": {"type": "string"}, + "str": {"type": "number"}, + "literal-literal": {"type": "string"}, + "computed": {"type": "string"}, + "42": {"type": "number"}, + "null": {"type": "null"}, + "true": {"type": "boolean"}, + "false": {"type": "boolean"}, + "foo": {"type": "number"}, + "array": { + "type": "array", + "items": { + "anyOf": [ + {"type": "number"}, + {"type": "string"}, + {"type": "boolean"}, + {"enum": [null]} + ] + } + }, + "seq": {"type": "number"} + }, + "required": [ + "num", + "str", + "literal-literal", + "computed", + "42", + "null", + "true", + "false", + "array", + "seq", + "foo" + ] + } + } +} diff --git a/tests/samples/variables/source.js b/tests/samples/variables/source.js new file mode 100644 index 0000000..03483a6 --- /dev/null +++ b/tests/samples/variables/source.js @@ -0,0 +1,37 @@ +export const obj: {foo: number} = {foo: 42}; + +type O = {foo: number}; + +export const obj2: O = {foo: 42}; + +export const obj3 = obj; + +export const num = 42; +export const str = ''; +export const nil = null; +export const empty = undefined; +const notExportableAndUseless = {useless: true} + +let otherNum = 42; + +export const obj4 = { + num: 42, + str: 'str', + 'literal-literal': 'literal', + ['computed']: 'computed', + 42: ++otherNum, + null: null, + true: true, + false: false, + array: [1, '', true, null], + seq: (1, '', 42) +} +export const obj5 = { + obj +} + +export const obj6 = { + ...obj4, + ...{num: '', str: 42}, + ...{...obj}, +} diff --git a/tests/samples/variables/types.yaml b/tests/samples/variables/types.yaml new file mode 100644 index 0000000..2a643cb --- /dev/null +++ b/tests/samples/variables/types.yaml @@ -0,0 +1,121 @@ +- kind: record + fields: + - name: foo + value: {kind: number, repr: f64} + required: true + id: [variables, obj] +- kind: record + fields: + - name: foo + value: {kind: number, repr: f64} + required: true + id: [variables, O] +- kind: reference + to: [variables, O] + id: [variables, obj2] +- kind: reference + to: [variables, obj] + id: [variables, obj3] +- kind: number + repr: f64 + id: [variables, num] +- kind: string + id: [variables, str] +- kind: literal + value: null + id: [variables, nil] +- kind: any + id: [variables, empty] +- kind: record + fields: + - name: num + value: {kind: number, repr: f64} + required: true + - name: str + value: {kind: string} + required: true + - name: literal-literal + value: {kind: string} + required: true + - name: computed + value: {kind: string} + required: true + - name: "42" + value: {kind: number, repr: f64} + required: true + - name: 'null' + value: {kind: literal, value: null} + required: true + - name: 'true' + value: {kind: boolean} + required: true + - name: 'false' + value: {kind: boolean} + required: true + - name: array + required: true + value: + kind: array + items: + kind: union + variants: + - {kind: number, repr: f64} + - {kind: string} + - {kind: boolean} + - {kind: literal, value: null} + - name: seq + value: {kind: number, repr: f64} + required: true + id: [variables, obj4] +- kind: record + fields: + - name: obj + value: + kind: reference + to: [variables, obj] + required: true + id: [variables, obj5] +- kind: record + fields: + - name: num + value: {kind: string} + required: true + - name: str + value: {kind: number, repr: f64} + required: true + - name: literal-literal + value: {kind: string} + required: true + - name: computed + value: {kind: string} + required: true + - name: "42" + value: {kind: number, repr: f64} + required: true + - name: 'null' + value: {kind: literal, value: null} + required: true + - name: 'true' + value: {kind: boolean} + required: true + - name: 'false' + value: {kind: boolean} + required: true + - name: array + required: true + value: + kind: array + items: + kind: union + variants: + - {kind: number, repr: f64} + - {kind: string} + - {kind: boolean} + - {kind: literal, value: null} + - name: seq + value: {kind: number, repr: f64} + required: true + - name: foo + value: {kind: number, repr: f64} + required: true + id: [variables, obj6]