mirror of
https://github.com/dolthub/dolt.git
synced 2026-05-22 11:39:20 -05:00
@@ -0,0 +1,245 @@
|
||||
// @flow
|
||||
|
||||
import {assert} from 'chai';
|
||||
import {newBlob} from './blob.js';
|
||||
import {newList} from './list.js';
|
||||
import {newMap} from './map.js';
|
||||
import {newSet} from './set.js';
|
||||
import {newStruct} from './struct.js';
|
||||
import {suite, test} from 'mocha';
|
||||
import assertSubtype from './assert-type.js';
|
||||
import type {Type} from './type.js';
|
||||
import {
|
||||
blobType,
|
||||
boolType,
|
||||
listOfValueType,
|
||||
makeListType,
|
||||
makeMapType,
|
||||
makeRefType,
|
||||
makeSetType,
|
||||
makeStructType,
|
||||
makeUnionType,
|
||||
mapOfValueType,
|
||||
numberType,
|
||||
setOfValueType,
|
||||
stringType,
|
||||
typeType,
|
||||
valueType,
|
||||
} from './type.js';
|
||||
import {equals} from './compare.js';
|
||||
import RefValue from './ref-value.js';
|
||||
|
||||
suite('validate type', () => {
|
||||
|
||||
function assertInvalid(t: Type, v) {
|
||||
assert.throws(() => { assertSubtype(t, v); });
|
||||
}
|
||||
|
||||
const allTypes = [
|
||||
boolType,
|
||||
numberType,
|
||||
stringType,
|
||||
blobType,
|
||||
typeType,
|
||||
valueType,
|
||||
];
|
||||
|
||||
function assertAll(t: Type, v) {
|
||||
for (const at of allTypes) {
|
||||
if (at === valueType || equals(t, at)) {
|
||||
assertSubtype(at, v);
|
||||
} else {
|
||||
assertInvalid(at, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test('primitives', () => {
|
||||
assertSubtype(boolType, true);
|
||||
assertSubtype(boolType, false);
|
||||
assertSubtype(numberType, 42);
|
||||
assertSubtype(stringType, 'abc');
|
||||
|
||||
assertInvalid(boolType, 1);
|
||||
assertInvalid(boolType, 'abc');
|
||||
assertInvalid(numberType, true);
|
||||
assertInvalid(stringType, 42);
|
||||
});
|
||||
|
||||
test('value', async () => {
|
||||
assertSubtype(valueType, true);
|
||||
assertSubtype(valueType, 1);
|
||||
assertSubtype(valueType, 'abc');
|
||||
const l = await newList([0, 1, 2, 3]);
|
||||
assertSubtype(valueType, l);
|
||||
});
|
||||
|
||||
test('blob', async () => {
|
||||
const b = await newBlob(new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]));
|
||||
assertAll(blobType, b);
|
||||
});
|
||||
|
||||
test('list', async () => {
|
||||
const listOfNumberType = makeListType(numberType);
|
||||
const l = await newList([0, 1, 2, 3]);
|
||||
assertSubtype(listOfNumberType, l);
|
||||
assertAll(listOfNumberType, l);
|
||||
|
||||
assertSubtype(listOfValueType, l);
|
||||
});
|
||||
|
||||
test('map', async () => {
|
||||
const mapOfNumberToStringType = makeMapType(numberType, stringType);
|
||||
const m = await newMap([0, 'a', 2, 'b']);
|
||||
assertSubtype(mapOfNumberToStringType, m);
|
||||
assertAll(mapOfNumberToStringType, m);
|
||||
|
||||
assertSubtype(mapOfValueType, m);
|
||||
});
|
||||
|
||||
test('set', async () => {
|
||||
const setOfNumberType = makeSetType(numberType);
|
||||
const s = await newSet([0, 1, 2, 3]);
|
||||
assertSubtype(setOfNumberType, s);
|
||||
assertAll(setOfNumberType, s);
|
||||
|
||||
assertSubtype(setOfValueType, s);
|
||||
});
|
||||
|
||||
test('type', () => {
|
||||
const t = makeSetType(numberType);
|
||||
assertSubtype(typeType, t);
|
||||
assertAll(typeType, t);
|
||||
|
||||
assertSubtype(valueType, t);
|
||||
});
|
||||
|
||||
test('struct', async () => {
|
||||
const type = makeStructType('Struct', {
|
||||
'x': boolType,
|
||||
});
|
||||
|
||||
const v = newStruct('Struct', {x: true});
|
||||
assertSubtype(type, v);
|
||||
assertAll(type, v);
|
||||
|
||||
assertSubtype(valueType, v);
|
||||
});
|
||||
|
||||
test('union', async () => {
|
||||
assertSubtype(makeUnionType([numberType]), 42);
|
||||
assertSubtype(makeUnionType([numberType, stringType]), 42);
|
||||
assertSubtype(makeUnionType([numberType, stringType]), 'hi');
|
||||
assertSubtype(makeUnionType([numberType, stringType, boolType]), 555);
|
||||
assertSubtype(makeUnionType([numberType, stringType, boolType]), 'hi');
|
||||
assertSubtype(makeUnionType([numberType, stringType, boolType]), true);
|
||||
|
||||
const lt = makeListType(makeUnionType([numberType, stringType]));
|
||||
assertSubtype(lt, await newList([1, 'hi', 2, 'bye']));
|
||||
|
||||
const st = makeSetType(stringType);
|
||||
assertSubtype(makeUnionType([st, numberType]), 42);
|
||||
assertSubtype(makeUnionType([st, numberType]), await newSet(['a', 'b']));
|
||||
|
||||
assertInvalid(makeUnionType([]), 42);
|
||||
assertInvalid(makeUnionType([stringType]), 42);
|
||||
assertInvalid(makeUnionType([stringType, boolType]), 42);
|
||||
assertInvalid(makeUnionType([st, stringType]), 42);
|
||||
assertInvalid(makeUnionType([st, numberType]), await newSet([1, 2]));
|
||||
});
|
||||
|
||||
test('empty list union', async () => {
|
||||
const lt = makeListType(makeUnionType([]));
|
||||
assertSubtype(lt, await newList([]));
|
||||
});
|
||||
|
||||
test('empty list', async () => {
|
||||
const lt = makeListType(numberType);
|
||||
assertSubtype(lt, await newList([]));
|
||||
|
||||
// List<> not a subtype of List<Number>
|
||||
assertInvalid(makeListType(makeUnionType([])), await newList([1]));
|
||||
});
|
||||
|
||||
test('empty set', async () => {
|
||||
const st = makeSetType(numberType);
|
||||
assertSubtype(st, await newSet([]));
|
||||
|
||||
// Set<> not a subtype of Set<Number>
|
||||
assertInvalid(makeSetType(makeUnionType([])), await newSet([1]));
|
||||
});
|
||||
|
||||
test('empty map', async () => {
|
||||
const mt = makeMapType(numberType, stringType);
|
||||
assertSubtype(mt, await newMap([]));
|
||||
|
||||
// Map<> not a subtype of Map<Number, Number>
|
||||
assertInvalid(makeMapType(makeUnionType([]), makeUnionType([])), await newMap([1, 2]));
|
||||
});
|
||||
|
||||
test('struct subtype by name', () => {
|
||||
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});
|
||||
|
||||
assertSubtype(namedT, namedV);
|
||||
assertInvalid(namedT, name2V);
|
||||
assertInvalid(namedT, anonV);
|
||||
|
||||
assertSubtype(anonT, namedV);
|
||||
assertSubtype(anonT, name2V);
|
||||
assertSubtype(anonT, anonV);
|
||||
});
|
||||
|
||||
test('struct subtype extra fields', () => {
|
||||
const at = makeStructType('', {});
|
||||
const bt = makeStructType('', {x: numberType});
|
||||
const ct = makeStructType('', {x: numberType, s: stringType});
|
||||
const av = newStruct('', {});
|
||||
const bv = newStruct('', {x: 1});
|
||||
const cv = newStruct('', {x: 2, s: 'hi'});
|
||||
|
||||
assertSubtype(at, av);
|
||||
assertInvalid(bt, av);
|
||||
assertInvalid(ct, av);
|
||||
|
||||
assertSubtype(at, bv);
|
||||
assertSubtype(bt, bv);
|
||||
assertInvalid(ct, bv);
|
||||
|
||||
assertSubtype(at, cv);
|
||||
assertSubtype(bt, cv);
|
||||
assertSubtype(ct, cv);
|
||||
});
|
||||
|
||||
test('struct subtype', async () => {
|
||||
const c1 = newStruct('Commit', {
|
||||
value: 1,
|
||||
parents: await newSet([]),
|
||||
});
|
||||
const t1 = makeStructType('Commit', {
|
||||
value: numberType,
|
||||
parents: makeSetType(makeUnionType([])),
|
||||
});
|
||||
assertSubtype(t1, c1);
|
||||
|
||||
const t11 = makeStructType('Commit', {
|
||||
value: numberType,
|
||||
parents: makeSetType(makeRefType(numberType /* placeholder */)),
|
||||
});
|
||||
t11.desc.fields['parents'].desc.elemTypes[0].desc.elemTypes[0] = t11;
|
||||
assertSubtype(t11, c1);
|
||||
|
||||
const c2 = newStruct('Commit', {
|
||||
value: 2,
|
||||
parents: await newSet([new RefValue(c1)]),
|
||||
});
|
||||
assertSubtype(t11, c2);
|
||||
|
||||
// struct { v: V, p: Set<> } <!
|
||||
// struct { v: V, p: Set<Ref<...>> }
|
||||
assertInvalid(t1, c2);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,90 @@
|
||||
// @flow
|
||||
|
||||
import {Kind, kindToString} from './noms-kind.js';
|
||||
import {CompoundDesc, getTypeOfValue} from './type.js';
|
||||
import type {Type} from './type.js';
|
||||
import {invariant} from './assert.js';
|
||||
import {equals} from './compare.js';
|
||||
import type {valueOrPrimitive} from './value.js';
|
||||
|
||||
/**
|
||||
* Ensures that the Noms value is a subtype of the Noms type. Throws a `TypeError` if not.
|
||||
* Here are the rules to determine if a value is a subtype: `<` is used to symbolize subtype.
|
||||
* ```
|
||||
* true < Bool
|
||||
* 4 < Number
|
||||
* "hi" < String
|
||||
* true < Bool | Number
|
||||
* [1, 2] < List<Number>
|
||||
* newSet(["hi"]) < Set<String>
|
||||
* newMap([1, "one"]) < Set<Number, String>
|
||||
* [1, "hi"] < List<Number | String>
|
||||
*
|
||||
* [] < List<>
|
||||
* [] < List<T> for all T
|
||||
* newSet([]) < Set<T> for all T
|
||||
* newMap([]) < Map<T, V> for all T and V
|
||||
*
|
||||
* newStruct("S", {x: 42}) < struct S {x: Number}
|
||||
* newStruct("S", {x: 42}) < struct "" {x: Number}, non nominal struct
|
||||
* newStruct("", {x: 42}) </ struct S {x: Number}, not a subtype
|
||||
* newStruct("S", {x: 42, y: true}) < struct S {x: Number}, extra fields OK.
|
||||
* ```
|
||||
*/
|
||||
export default function assertSubtype(requiredType: Type, v: valueOrPrimitive): void {
|
||||
assert(isSubtype(requiredType, getTypeOfValue(v)), v, requiredType);
|
||||
}
|
||||
|
||||
function isSubtype(requiredType: Type, concreteType: Type): boolean {
|
||||
if (equals(requiredType, concreteType)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (requiredType.kind === Kind.Union) {
|
||||
const {desc} = requiredType;
|
||||
invariant(desc instanceof CompoundDesc);
|
||||
return desc.elemTypes.some(t => isSubtype(t, concreteType));
|
||||
}
|
||||
|
||||
if (requiredType.kind !== concreteType.kind) {
|
||||
return requiredType.kind === Kind.Value;
|
||||
}
|
||||
|
||||
const requiredDesc = requiredType.desc;
|
||||
const concreteDesc = concreteType.desc;
|
||||
if (requiredDesc instanceof CompoundDesc) {
|
||||
const concreteTypeElemTypes = concreteDesc.elemTypes;
|
||||
return requiredDesc.elemTypes.every((t, i) => compoundSubtype(t, concreteTypeElemTypes[i]));
|
||||
}
|
||||
|
||||
if (requiredType.kind === Kind.Struct) {
|
||||
if (requiredDesc.name !== '' && requiredDesc.name !== concreteDesc.name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const fields: Array<[string, Type]> = [];
|
||||
requiredDesc.forEachField((name: string, type: Type) => {
|
||||
fields.push([name, type]);
|
||||
});
|
||||
return fields.every(f => {
|
||||
const at = concreteDesc.fields[f[0]];
|
||||
return at && isSubtype(f[1], at);
|
||||
});
|
||||
}
|
||||
|
||||
invariant(false);
|
||||
}
|
||||
|
||||
function compoundSubtype(requiredType: Type, concreteType: Type): boolean {
|
||||
// In a compound type it is OK to have an empty union.
|
||||
if (concreteType.kind === Kind.Union && concreteType.desc.elemTypes.length === 0) {
|
||||
return true;
|
||||
}
|
||||
return isSubtype(requiredType, concreteType);
|
||||
}
|
||||
|
||||
function assert(b, v, t) {
|
||||
if (!b) {
|
||||
throw new TypeError(`${v} is not a valid ${kindToString(t.kind)}`);
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -8,7 +8,7 @@ import {invariant} from './assert.js';
|
||||
import {isPrimitive} from './primitives.js';
|
||||
import {Kind} from './noms-kind.js';
|
||||
import {Value} from './value.js';
|
||||
import validateType from './validate-type.js';
|
||||
import assertSubtype from './assert-type.js';
|
||||
|
||||
type StructData = {[key: string]: valueOrPrimitive};
|
||||
|
||||
@@ -70,7 +70,7 @@ export default class Struct extends Value {
|
||||
function validate(type: Type, data: StructData): void {
|
||||
type.desc.forEachField((name: string, type: Type) => {
|
||||
const value = data[name];
|
||||
validateType(type, value);
|
||||
assertSubtype(type, value);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,172 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import {assert} from 'chai';
|
||||
import {newBlob} from './blob.js';
|
||||
import {newList} from './list.js';
|
||||
import {newMap} from './map.js';
|
||||
import {newSet} from './set.js';
|
||||
import {newStruct} from './struct.js';
|
||||
import {suite, test} from 'mocha';
|
||||
import validateType from './validate-type.js';
|
||||
import type {Type} from './type.js';
|
||||
import {
|
||||
blobType,
|
||||
boolType,
|
||||
listOfValueType,
|
||||
makeListType,
|
||||
makeMapType,
|
||||
makeSetType,
|
||||
makeStructType,
|
||||
makeUnionType,
|
||||
mapOfValueType,
|
||||
numberType,
|
||||
setOfValueType,
|
||||
stringType,
|
||||
typeType,
|
||||
valueType,
|
||||
} from './type.js';
|
||||
import {equals} from './compare.js';
|
||||
|
||||
suite('validate type', () => {
|
||||
|
||||
function assertInvalid(t: Type, v) {
|
||||
assert.throws(() => { validateType(t, v); });
|
||||
}
|
||||
|
||||
const allTypes = [
|
||||
boolType,
|
||||
numberType,
|
||||
stringType,
|
||||
blobType,
|
||||
typeType,
|
||||
valueType,
|
||||
];
|
||||
|
||||
function assertAll(t: Type, v) {
|
||||
for (const at of allTypes) {
|
||||
if (at === valueType || equals(t, at)) {
|
||||
validateType(at, v);
|
||||
} else {
|
||||
assertInvalid(at, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test('primitives', () => {
|
||||
validateType(boolType, true);
|
||||
validateType(boolType, false);
|
||||
validateType(numberType, 42);
|
||||
validateType(stringType, 'abc');
|
||||
|
||||
assertInvalid(boolType, 1);
|
||||
assertInvalid(boolType, 'abc');
|
||||
assertInvalid(numberType, true);
|
||||
assertInvalid(stringType, 42);
|
||||
});
|
||||
|
||||
test('value', async () => {
|
||||
validateType(valueType, true);
|
||||
validateType(valueType, 1);
|
||||
validateType(valueType, 'abc');
|
||||
const l = await newList([0, 1, 2, 3]);
|
||||
validateType(valueType, l);
|
||||
|
||||
assertInvalid(valueType, null);
|
||||
assertInvalid(valueType, undefined);
|
||||
assertInvalid(valueType, {});
|
||||
});
|
||||
|
||||
test('blob', async () => {
|
||||
const b = await newBlob(new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]));
|
||||
assertAll(blobType, b);
|
||||
});
|
||||
|
||||
test('list', async () => {
|
||||
const listOfNumberType = makeListType(numberType);
|
||||
const l = await newList([0, 1, 2, 3]);
|
||||
validateType(listOfNumberType, l);
|
||||
assertAll(listOfNumberType, l);
|
||||
|
||||
validateType(listOfValueType, l);
|
||||
});
|
||||
|
||||
test('map', async () => {
|
||||
const mapOfNumberToStringType = makeMapType(numberType, stringType);
|
||||
const m = await newMap([0, 'a', 2, 'b']);
|
||||
validateType(mapOfNumberToStringType, m);
|
||||
assertAll(mapOfNumberToStringType, m);
|
||||
|
||||
validateType(mapOfValueType, m);
|
||||
});
|
||||
|
||||
test('set', async () => {
|
||||
const setOfNumberType = makeSetType(numberType);
|
||||
const s = await newSet([0, 1, 2, 3]);
|
||||
validateType(setOfNumberType, s);
|
||||
assertAll(setOfNumberType, s);
|
||||
|
||||
validateType(setOfValueType, s);
|
||||
});
|
||||
|
||||
test('type', () => {
|
||||
const t = makeSetType(numberType);
|
||||
validateType(typeType, t);
|
||||
assertAll(typeType, t);
|
||||
|
||||
validateType(valueType, t);
|
||||
});
|
||||
|
||||
test('struct', async () => {
|
||||
const type = makeStructType('Struct', {
|
||||
'x': boolType,
|
||||
});
|
||||
|
||||
const v = newStruct('Struct', {x: true});
|
||||
validateType(type, v);
|
||||
assertAll(type, v);
|
||||
|
||||
validateType(valueType, v);
|
||||
});
|
||||
|
||||
test('union', async () => {
|
||||
validateType(makeUnionType([numberType]), 42);
|
||||
validateType(makeUnionType([numberType, stringType]), 42);
|
||||
validateType(makeUnionType([numberType, stringType]), 'hi');
|
||||
validateType(makeUnionType([numberType, stringType, boolType]), 555);
|
||||
validateType(makeUnionType([numberType, stringType, boolType]), 'hi');
|
||||
validateType(makeUnionType([numberType, stringType, boolType]), true);
|
||||
|
||||
const lt = makeListType(makeUnionType([numberType, stringType]));
|
||||
validateType(lt, await newList([1, 'hi', 2, 'bye']));
|
||||
|
||||
const st = makeSetType(stringType);
|
||||
validateType(makeUnionType([st, numberType]), 42);
|
||||
validateType(makeUnionType([st, numberType]), await newSet(['a', 'b']));
|
||||
|
||||
assertInvalid(makeUnionType([]), 42);
|
||||
assertInvalid(makeUnionType([stringType]), 42);
|
||||
assertInvalid(makeUnionType([stringType, boolType]), 42);
|
||||
assertInvalid(makeUnionType([st, stringType]), 42);
|
||||
assertInvalid(makeUnionType([st, numberType]), await newSet([1, 2]));
|
||||
});
|
||||
|
||||
test('empty list union', async () => {
|
||||
const lt = makeListType(makeUnionType([]));
|
||||
validateType(lt, await newList([]));
|
||||
});
|
||||
|
||||
test('empty list', async () => {
|
||||
const lt = makeListType(numberType);
|
||||
validateType(lt, await newList([]));
|
||||
});
|
||||
|
||||
test('empty set', async () => {
|
||||
const st = makeSetType(numberType);
|
||||
validateType(st, await newSet([]));
|
||||
});
|
||||
|
||||
test('empty map', async () => {
|
||||
const mt = makeMapType(numberType, stringType);
|
||||
validateType(mt, await newMap([]));
|
||||
});
|
||||
});
|
||||
@@ -1,94 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import {Kind, kindToString} from './noms-kind.js';
|
||||
import {CompoundDesc, getTypeOfValue} from './type.js';
|
||||
import type {Type} from './type.js';
|
||||
import {Value} from './value.js';
|
||||
import {invariant} from './assert.js';
|
||||
import {equals} from './compare.js';
|
||||
|
||||
export default function validateType(t: Type, v: any): void {
|
||||
switch (t.kind) {
|
||||
case Kind.Bool:
|
||||
assertTypeof(v, 'boolean', t);
|
||||
return;
|
||||
|
||||
case Kind.Number:
|
||||
assertTypeof(v, 'number', t);
|
||||
// TODO: Validate value.
|
||||
return;
|
||||
|
||||
case Kind.String:
|
||||
assertTypeof(v, 'string', t);
|
||||
return;
|
||||
|
||||
case Kind.Blob:
|
||||
case Kind.List:
|
||||
case Kind.Map:
|
||||
case Kind.Ref:
|
||||
case Kind.Set:
|
||||
case Kind.Struct:
|
||||
case Kind.Type:
|
||||
assertSubtype(v, t);
|
||||
return;
|
||||
|
||||
case Kind.Value:
|
||||
case Kind.Union:
|
||||
assert(subtype(t, getTypeOfValue(v)), v, t);
|
||||
break;
|
||||
|
||||
case Kind.Parent:
|
||||
default:
|
||||
throw new Error('unreachable');
|
||||
}
|
||||
}
|
||||
|
||||
function assertSubtype(v: any, t: Type) {
|
||||
assert(v instanceof Value, v, t);
|
||||
assert(subtype(t, v.type), v, t);
|
||||
}
|
||||
|
||||
function subtype(expected: Type, actual: Type): boolean {
|
||||
if (equals(expected, actual)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (expected.kind === Kind.Union) {
|
||||
const {desc} = expected;
|
||||
invariant(desc instanceof CompoundDesc);
|
||||
return desc.elemTypes.some(t => subtype(t, actual));
|
||||
}
|
||||
|
||||
if (expected.kind !== actual.kind) {
|
||||
return expected.kind === Kind.Value;
|
||||
}
|
||||
|
||||
if (expected.desc instanceof CompoundDesc) {
|
||||
const actualElemTypes = actual.desc.elemTypes;
|
||||
return expected.desc.elemTypes.every((t, i) => compoundSubtype(t, actualElemTypes[i]));
|
||||
}
|
||||
|
||||
invariant(false);
|
||||
}
|
||||
|
||||
function compoundSubtype(expected: Type, actual: Type): boolean {
|
||||
// In a compound type it is OK to have an empty union.
|
||||
if (actual.kind === Kind.Union && actual.desc.elemTypes.length === 0) {
|
||||
return true;
|
||||
}
|
||||
return subtype(expected, actual);
|
||||
}
|
||||
|
||||
function makeTypeError(v: any, t: Type) {
|
||||
return new TypeError(`${v} is not a valid ${kindToString(t.kind)}`);
|
||||
}
|
||||
|
||||
function assert(b, v, t) {
|
||||
if (!b) {
|
||||
throw makeTypeError(v, t);
|
||||
}
|
||||
}
|
||||
|
||||
function assertTypeof(v: any, s: string, t: Type) {
|
||||
assert(typeof v === s, v, t);
|
||||
}
|
||||
+38
-15
@@ -2,34 +2,57 @@ package types
|
||||
|
||||
import "github.com/attic-labs/noms/d"
|
||||
|
||||
func assertType(t *Type, v Value) {
|
||||
if !subtype(t, v.Type()) {
|
||||
func assertSubtype(t *Type, v Value) {
|
||||
if !isSubtype(t, v.Type()) {
|
||||
d.Chk.Fail("Invalid type", "Expected: %s, found: %s", t.Describe(), v.Type().Describe())
|
||||
}
|
||||
}
|
||||
|
||||
func subtype(expected, actual *Type) bool {
|
||||
if expected.Equals(actual) {
|
||||
func isSubtype(requiredType, concreteType *Type) bool {
|
||||
if requiredType.Equals(concreteType) {
|
||||
return true
|
||||
}
|
||||
|
||||
if expected.Kind() == UnionKind {
|
||||
for _, t := range expected.Desc.(CompoundDesc).ElemTypes {
|
||||
if subtype(t, actual) {
|
||||
if requiredType.Kind() == UnionKind {
|
||||
for _, t := range requiredType.Desc.(CompoundDesc).ElemTypes {
|
||||
if isSubtype(t, concreteType) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if expected.Kind() != actual.Kind() {
|
||||
return expected.Kind() == ValueKind
|
||||
if requiredType.Kind() != concreteType.Kind() {
|
||||
return requiredType.Kind() == ValueKind
|
||||
}
|
||||
|
||||
if desc, ok := expected.Desc.(CompoundDesc); ok {
|
||||
actualElemTypes := actual.Desc.(CompoundDesc).ElemTypes
|
||||
if desc, ok := requiredType.Desc.(CompoundDesc); ok {
|
||||
concreteElemTypes := concreteType.Desc.(CompoundDesc).ElemTypes
|
||||
for i, t := range desc.ElemTypes {
|
||||
if !compoundSubtype(t, actualElemTypes[i]) {
|
||||
if !compoundSubtype(t, concreteElemTypes[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
if requiredType.Kind() == StructKind {
|
||||
requiredDesc := requiredType.Desc.(StructDesc)
|
||||
concreteDesc := concreteType.Desc.(StructDesc)
|
||||
if requiredDesc.Name != "" && requiredDesc.Name != concreteDesc.Name {
|
||||
return false
|
||||
}
|
||||
type Entry struct {
|
||||
name string
|
||||
t *Type
|
||||
}
|
||||
entries := make([]Entry, 0, len(requiredDesc.Fields))
|
||||
requiredDesc.IterFields(func(name string, t *Type) {
|
||||
entries = append(entries, Entry{name, t})
|
||||
})
|
||||
for _, entry := range entries {
|
||||
at, ok := concreteDesc.Fields[entry.name]
|
||||
if !ok || !isSubtype(entry.t, at) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -39,10 +62,10 @@ func subtype(expected, actual *Type) bool {
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
func compoundSubtype(expected, actual *Type) bool {
|
||||
func compoundSubtype(requiredType, concreteType *Type) bool {
|
||||
// In a compound type it is OK to have an empty union.
|
||||
if actual.Kind() == UnionKind && len(actual.Desc.(CompoundDesc).ElemTypes) == 0 {
|
||||
if concreteType.Kind() == UnionKind && len(concreteType.Desc.(CompoundDesc).ElemTypes) == 0 {
|
||||
return true
|
||||
}
|
||||
return subtype(expected, actual)
|
||||
return isSubtype(requiredType, concreteType)
|
||||
}
|
||||
|
||||
+106
-37
@@ -7,14 +7,10 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func validateType(t *Type, v Value) {
|
||||
assertType(t, v)
|
||||
}
|
||||
|
||||
func assertInvalid(tt *testing.T, t *Type, v Value) {
|
||||
assert := assert.New(tt)
|
||||
assert.Panics(func() {
|
||||
assertType(t, v)
|
||||
assertSubtype(t, v)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -30,7 +26,7 @@ func assertAll(tt *testing.T, t *Type, v Value) {
|
||||
|
||||
for _, at := range allTypes {
|
||||
if at == ValueType || t.Equals(at) {
|
||||
validateType(at, v)
|
||||
assertSubtype(at, v)
|
||||
} else {
|
||||
assertInvalid(tt, at, v)
|
||||
}
|
||||
@@ -38,10 +34,10 @@ func assertAll(tt *testing.T, t *Type, v Value) {
|
||||
}
|
||||
|
||||
func TestAssertTypePrimitives(t *testing.T) {
|
||||
validateType(BoolType, Bool(true))
|
||||
validateType(BoolType, Bool(false))
|
||||
validateType(NumberType, Number(42))
|
||||
validateType(StringType, NewString("abc"))
|
||||
assertSubtype(BoolType, Bool(true))
|
||||
assertSubtype(BoolType, Bool(false))
|
||||
assertSubtype(NumberType, Number(42))
|
||||
assertSubtype(StringType, NewString("abc"))
|
||||
|
||||
assertInvalid(t, BoolType, Number(1))
|
||||
assertInvalid(t, BoolType, NewString("abc"))
|
||||
@@ -50,11 +46,11 @@ func TestAssertTypePrimitives(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAssertTypeValue(t *testing.T) {
|
||||
validateType(ValueType, Bool(true))
|
||||
validateType(ValueType, Number(1))
|
||||
validateType(ValueType, NewString("abc"))
|
||||
assertSubtype(ValueType, Bool(true))
|
||||
assertSubtype(ValueType, Number(1))
|
||||
assertSubtype(ValueType, NewString("abc"))
|
||||
l := NewList(Number(0), Number(1), Number(2), Number(3))
|
||||
validateType(ValueType, l)
|
||||
assertSubtype(ValueType, l)
|
||||
}
|
||||
|
||||
func TestAssertTypeBlob(t *testing.T) {
|
||||
@@ -65,32 +61,32 @@ func TestAssertTypeBlob(t *testing.T) {
|
||||
func TestAssertTypeList(tt *testing.T) {
|
||||
listOfNumberType := MakeListType(NumberType)
|
||||
l := NewList(Number(0), Number(1), Number(2), Number(3))
|
||||
validateType(listOfNumberType, l)
|
||||
assertSubtype(listOfNumberType, l)
|
||||
assertAll(tt, listOfNumberType, l)
|
||||
validateType(MakeListType(ValueType), l)
|
||||
assertSubtype(MakeListType(ValueType), l)
|
||||
}
|
||||
|
||||
func TestAssertTypeMap(tt *testing.T) {
|
||||
mapOfNumberToStringType := MakeMapType(NumberType, StringType)
|
||||
m := NewMap(Number(0), NewString("a"), Number(2), NewString("b"))
|
||||
validateType(mapOfNumberToStringType, m)
|
||||
assertSubtype(mapOfNumberToStringType, m)
|
||||
assertAll(tt, mapOfNumberToStringType, m)
|
||||
validateType(MakeMapType(ValueType, ValueType), m)
|
||||
assertSubtype(MakeMapType(ValueType, ValueType), m)
|
||||
}
|
||||
|
||||
func TestAssertTypeSet(tt *testing.T) {
|
||||
setOfNumberType := MakeSetType(NumberType)
|
||||
s := NewSet(Number(0), Number(1), Number(2), Number(3))
|
||||
validateType(setOfNumberType, s)
|
||||
assertSubtype(setOfNumberType, s)
|
||||
assertAll(tt, setOfNumberType, s)
|
||||
validateType(MakeSetType(ValueType), s)
|
||||
assertSubtype(MakeSetType(ValueType), s)
|
||||
}
|
||||
|
||||
func TestAssertTypeType(tt *testing.T) {
|
||||
t := MakeSetType(NumberType)
|
||||
validateType(TypeType, t)
|
||||
assertSubtype(TypeType, t)
|
||||
assertAll(tt, TypeType, t)
|
||||
validateType(ValueType, t)
|
||||
assertSubtype(ValueType, t)
|
||||
}
|
||||
|
||||
func TestAssertTypeStruct(tt *testing.T) {
|
||||
@@ -99,25 +95,25 @@ func TestAssertTypeStruct(tt *testing.T) {
|
||||
})
|
||||
|
||||
v := NewStruct("Struct", structData{"x": Bool(true)})
|
||||
validateType(t, v)
|
||||
assertSubtype(t, v)
|
||||
assertAll(tt, t, v)
|
||||
validateType(ValueType, v)
|
||||
assertSubtype(ValueType, v)
|
||||
}
|
||||
|
||||
func TestAssertTypeUnion(tt *testing.T) {
|
||||
validateType(MakeUnionType(NumberType), Number(42))
|
||||
validateType(MakeUnionType(NumberType, StringType), Number(42))
|
||||
validateType(MakeUnionType(NumberType, StringType), NewString("hi"))
|
||||
validateType(MakeUnionType(NumberType, StringType, BoolType), Number(555))
|
||||
validateType(MakeUnionType(NumberType, StringType, BoolType), NewString("hi"))
|
||||
validateType(MakeUnionType(NumberType, StringType, BoolType), Bool(true))
|
||||
assertSubtype(MakeUnionType(NumberType), Number(42))
|
||||
assertSubtype(MakeUnionType(NumberType, StringType), Number(42))
|
||||
assertSubtype(MakeUnionType(NumberType, StringType), NewString("hi"))
|
||||
assertSubtype(MakeUnionType(NumberType, StringType, BoolType), Number(555))
|
||||
assertSubtype(MakeUnionType(NumberType, StringType, BoolType), NewString("hi"))
|
||||
assertSubtype(MakeUnionType(NumberType, StringType, BoolType), Bool(true))
|
||||
|
||||
lt := MakeListType(MakeUnionType(NumberType, StringType))
|
||||
validateType(lt, NewList(Number(1), NewString("hi"), Number(2), NewString("bye")))
|
||||
assertSubtype(lt, NewList(Number(1), NewString("hi"), Number(2), NewString("bye")))
|
||||
|
||||
st := MakeSetType(StringType)
|
||||
validateType(MakeUnionType(st, NumberType), Number(42))
|
||||
validateType(MakeUnionType(st, NumberType), NewSet(NewString("a"), NewString("b")))
|
||||
assertSubtype(MakeUnionType(st, NumberType), Number(42))
|
||||
assertSubtype(MakeUnionType(st, NumberType), NewSet(NewString("a"), NewString("b")))
|
||||
|
||||
assertInvalid(tt, MakeUnionType(), Number(42))
|
||||
assertInvalid(tt, MakeUnionType(StringType), Number(42))
|
||||
@@ -128,20 +124,93 @@ func TestAssertTypeUnion(tt *testing.T) {
|
||||
|
||||
func TestAssertTypeEmptyListUnion(tt *testing.T) {
|
||||
lt := MakeListType(MakeUnionType())
|
||||
validateType(lt, NewList())
|
||||
assertSubtype(lt, NewList())
|
||||
}
|
||||
|
||||
func TestAssertTypeEmptyList(tt *testing.T) {
|
||||
lt := MakeListType(NumberType)
|
||||
validateType(lt, NewList())
|
||||
assertSubtype(lt, NewList())
|
||||
|
||||
// List<> not a subtype of List<Number>
|
||||
assertInvalid(tt, MakeListType(MakeUnionType()), NewList(Number(1)))
|
||||
}
|
||||
|
||||
func TestAssertTypeEmptySet(tt *testing.T) {
|
||||
st := MakeSetType(NumberType)
|
||||
validateType(st, NewSet())
|
||||
assertSubtype(st, NewSet())
|
||||
|
||||
// Set<> not a subtype of Set<Number>
|
||||
assertInvalid(tt, MakeSetType(MakeUnionType()), NewSet(Number(1)))
|
||||
}
|
||||
|
||||
func TestAssertTypeEmptyMap(tt *testing.T) {
|
||||
mt := MakeMapType(NumberType, StringType)
|
||||
validateType(mt, NewMap())
|
||||
assertSubtype(mt, NewMap())
|
||||
|
||||
// Map<> not a subtype of Map<Number, Number>
|
||||
assertInvalid(tt, MakeMapType(MakeUnionType(), MakeUnionType()), NewMap(Number(1), Number(2)))
|
||||
}
|
||||
|
||||
func TestAssertTypeStructSubtypeByName(tt *testing.T) {
|
||||
namedT := MakeStructType("Name", TypeMap{"x": NumberType})
|
||||
anonT := MakeStructType("", TypeMap{"x": NumberType})
|
||||
namedV := NewStruct("Name", structData{"x": Number(42)})
|
||||
name2V := NewStruct("foo", structData{"x": Number(42)})
|
||||
anonV := NewStruct("", structData{"x": Number(42)})
|
||||
|
||||
assertSubtype(namedT, namedV)
|
||||
assertInvalid(tt, namedT, name2V)
|
||||
assertInvalid(tt, namedT, anonV)
|
||||
|
||||
assertSubtype(anonT, namedV)
|
||||
assertSubtype(anonT, name2V)
|
||||
assertSubtype(anonT, anonV)
|
||||
}
|
||||
|
||||
func TestAssertTypeStructSubtypeExtraFields(tt *testing.T) {
|
||||
at := MakeStructType("", TypeMap{})
|
||||
bt := MakeStructType("", TypeMap{"x": NumberType})
|
||||
ct := MakeStructType("", TypeMap{"x": NumberType, "s": StringType})
|
||||
av := NewStruct("", structData{})
|
||||
bv := NewStruct("", structData{"x": Number(1)})
|
||||
cv := NewStruct("", structData{"x": Number(2), "s": NewString("hi")})
|
||||
|
||||
assertSubtype(at, av)
|
||||
assertInvalid(tt, bt, av)
|
||||
assertInvalid(tt, ct, av)
|
||||
|
||||
assertSubtype(at, bv)
|
||||
assertSubtype(bt, bv)
|
||||
assertInvalid(tt, ct, bv)
|
||||
|
||||
assertSubtype(at, cv)
|
||||
assertSubtype(bt, cv)
|
||||
assertSubtype(ct, cv)
|
||||
}
|
||||
|
||||
func TestAssertTypeStructSubtype(tt *testing.T) {
|
||||
c1 := NewStruct("Commit", structData{
|
||||
"value": Number(1),
|
||||
"parents": NewSet(),
|
||||
})
|
||||
t1 := MakeStructType("Commit", TypeMap{
|
||||
"value": NumberType,
|
||||
"parents": MakeSetType(MakeUnionType()),
|
||||
})
|
||||
assertSubtype(t1, c1)
|
||||
|
||||
t11 := MakeStructType("Commit", TypeMap{
|
||||
"value": NumberType,
|
||||
"parents": MakeSetType(MakeRefType(NumberType /* placeholder */)),
|
||||
})
|
||||
t11.Desc.(StructDesc).Fields["parents"].Desc.(CompoundDesc).ElemTypes[0].Desc.(CompoundDesc).ElemTypes[0] = t11
|
||||
assertSubtype(t11, c1)
|
||||
|
||||
c2 := NewStruct("Commit", structData{
|
||||
"value": Number(2),
|
||||
"parents": NewSet(NewRef(c1)),
|
||||
})
|
||||
assertSubtype(t11, c2)
|
||||
|
||||
// t3 :=
|
||||
}
|
||||
|
||||
+2
-2
@@ -35,7 +35,7 @@ func NewStructWithType(t *Type, data structData) Struct {
|
||||
for name, t := range desc.Fields {
|
||||
v, ok := data[name]
|
||||
d.Chk.True(ok, "Missing required field %s", name)
|
||||
assertType(t, v)
|
||||
assertSubtype(t, v)
|
||||
newData[name] = v
|
||||
}
|
||||
return newStructFromData(newData, t)
|
||||
@@ -102,7 +102,7 @@ func (s Struct) Get(n string) Value {
|
||||
func (s Struct) Set(n string, v Value) Struct {
|
||||
t, ok := s.findField(n)
|
||||
d.Chk.True(ok, "Struct has no field %s", n)
|
||||
assertType(t, v)
|
||||
assertSubtype(t, v)
|
||||
data := make(structData, len(s.data))
|
||||
for k, v := range s.data {
|
||||
data[k] = v
|
||||
|
||||
Reference in New Issue
Block a user