Struct subtype (#1524)

* Subtype checking for structs

Towards #1491
This commit is contained in:
Erik Arvidsson
2016-05-18 09:06:53 -07:00
parent cefb014d15
commit 17a78061c0
8 changed files with 483 additions and 322 deletions
+245
View File
@@ -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);
});
});
+90
View File
@@ -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
View File
@@ -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);
});
}
-172
View File
@@ -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([]));
});
});
-94
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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