Rearrange Dictionary type parameters for improved Record type inference

This commit is contained in:
Tony Garnock-Jones 2021-03-17 12:20:06 +01:00
parent 8f2da8f8db
commit 178f528bf0
8 changed files with 31 additions and 24 deletions

View File

@ -122,8 +122,8 @@ export class Decoder<T = never> implements TypedDecoder<T> {
return this.includeAnnotations ? new Annotated(v) : v;
}
static dictionaryFromArray<T>(vs: Value<T>[]): Dictionary<Value<T>, T> {
const d = new Dictionary<Value<T>, T>();
static dictionaryFromArray<T>(vs: Value<T>[]): Dictionary<T> {
const d = new Dictionary<T>();
if (vs.length % 2) throw new DecodeError("Missing dictionary value");
for (let i = 0; i < vs.length; i += 2) {
d.set(vs[i], vs[i+1]);

View File

@ -75,14 +75,14 @@ export class KeyedDictionary<K extends Value<T>, V, T = DefaultPointer> extends
}
}
export class Dictionary<V, T = DefaultPointer> extends KeyedDictionary<Value<T>, V, T> {
static isDictionary<V, T = DefaultPointer>(x: any): x is Dictionary<V, T> {
export class Dictionary<T = DefaultPointer, V = Value<T>> extends KeyedDictionary<Value<T>, V, T> {
static isDictionary<T = DefaultPointer, V = Value<T>>(x: any): x is Dictionary<T, V> {
return x?.[DictionaryType] === 'Dictionary';
}
static fromJS<V = DefaultPointer, T = DefaultPointer>(x: object): Dictionary<Value<V>, T> {
if (Dictionary.isDictionary<Value<V>, T>(x)) return x as Dictionary<Value<V>, T>;
const d = new Dictionary<Value<V>, T>();
static fromJS<T = DefaultPointer, V = DefaultPointer>(x: object): Dictionary<T, Value<V>> {
if (Dictionary.isDictionary<T, Value<V>>(x)) return x;
const d = new Dictionary<T, Value<V>>();
Object.entries(x).forEach(([key, value]) => d.set(key, fromJS(value)));
return d;
}

View File

@ -19,7 +19,7 @@ export interface FoldMethods<T, R> {
record(r: Record<Value<T>, Tuple<Value<T>>, T>, k: Fold<T, R>): R;
array(a: Array<Value<T>>, k: Fold<T, R>): R;
set(s: Set<T>, k: Fold<T, R>): R;
dictionary(d: Dictionary<Value<T>, T>, k: Fold<T, R>): R;
dictionary(d: Dictionary<T>, k: Fold<T, R>): R;
annotated(a: Annotated<T>, k: Fold<T, R>): R;
@ -57,7 +57,7 @@ export abstract class ValueFold<T, R = T> implements FoldMethods<T, Value<R>> {
set(s: Set<T>, k: Fold<T, Value<R>>): Value<R> {
return s.map(k);
}
dictionary(d: Dictionary<Value<T>, T>, k: Fold<T, Value<R>>): Value<R> {
dictionary(d: Dictionary<T>, k: Fold<T, Value<R>>): Value<R> {
return d.mapEntries(([key, value]) => [k(key), k(value)]);
}
annotated(a: Annotated<T>, k: Fold<T, Value<R>>): Value<R> {
@ -110,7 +110,7 @@ export function fold<T, R>(v: Value<T>, o: FoldMethods<T, R>): R {
return o.array(v, walk);
} else if (Set.isSet<T>(v)) {
return o.set(v, walk);
} else if (Dictionary.isDictionary<Value<T>, T>(v)) {
} else if (Dictionary.isDictionary<T>(v)) {
return o.dictionary(v, walk);
} else if (Annotated.isAnnotated<T>(v)) {
return o.annotated(v, walk);
@ -153,7 +153,7 @@ export function isPointer<T>(v: Value<T>): v is T {
},
array(_a: Array<Value<T>>, _k: Fold<T, boolean>): boolean { return false; },
set(_s: Set<T>, _k: Fold<T, boolean>): boolean { return false; },
dictionary(_d: Dictionary<Value<T>, T>, _k: Fold<T, boolean>): boolean {
dictionary(_d: Dictionary<T>, _k: Fold<T, boolean>): boolean {
return false;
},

View File

@ -227,8 +227,8 @@ export class Reader<T> {
}
}
readDictionary(): Dictionary<Value<T>, T> {
return this.seq(new Dictionary<Value<T>, T>(),
readDictionary(): Dictionary<T> {
return this.seq(new Dictionary<T>(),
(k, acc) => {
this.skipws();
switch (this.peek()) {

View File

@ -26,13 +26,20 @@ export interface RecordConstructorInfo<L extends Value<T>, T = DefaultPointer> {
arity: number;
}
export type InferredRecordType<L, FieldsType extends Tuple<any>> =
L extends symbol ? (FieldsType extends Tuple<Value<infer T>>
? (Exclude<T, never> extends symbol ? Record<L, FieldsType, never> : Record<L, FieldsType, T>)
: (FieldsType extends Tuple<Value<never>>
? Record<L, FieldsType, never>
: "TYPE_ERROR_cannotInferFieldsType" & [never])) :
L extends Value<infer T> ? (FieldsType extends Tuple<Value<T>>
? Record<L, FieldsType, T>
: "TYPE_ERROR_cannotMatchFieldsTypeToLabelType" & [never]) :
"TYPE_ERROR_cannotInferPointerType" & [never];
export function Record<L, FieldsType extends Tuple<any>>(
label: L, fields: FieldsType):
L extends Value<infer T>
? (FieldsType extends Tuple<Value<T>>
? Record<L, FieldsType, T>
: never)
: never
label: L,
fields: FieldsType): InferredRecordType<L, FieldsType>
{
(fields as any).label = label;
return fields as any;

View File

@ -33,7 +33,7 @@ export function strip<T = DefaultPointer>(
return (v.item as Value<T>[]).map(walk);
} else if (Set.isSet<T>(v.item)) {
return v.item.map(walk);
} else if (Dictionary.isDictionary<Value<T>, T>(v.item)) {
} else if (Dictionary.isDictionary<T>(v.item)) {
return v.item.mapEntries((e) => [walk(e[0]), walk(e[1])]);
} else {
return v.item;

View File

@ -28,4 +28,4 @@ export type Compound<T = DefaultPointer> =
// Value<T> to any.
| Array<Value<T>>
| Set<T>
| Dictionary<Value<T>, T>;
| Dictionary<T>;

View File

@ -140,7 +140,7 @@ describe('encoding and decoding pointers', () => {
it('should store pointers embedded in map keys correctly', () => {
const A1 = ({a: 1});
const A2 = ({a: 1});
const m = new Dictionary<number, object>();
const m = new Dictionary<object, number>();
m.set([A1], 1);
m.set([A2], 2);
expect(m.get(A1)).toBeUndefined();
@ -157,7 +157,7 @@ describe('common test suite', () => {
const samples = decodeWithAnnotations(samples_bin, { decodePointer: decodeDefaultPointer });
const TestCases = Record.makeConstructor<{
cases: Dictionary<Value<DefaultPointer>, DefaultPointer>
cases: Dictionary<DefaultPointer>
}>()(Symbol.for('TestCases'), ['cases']);
type TestCases = ReturnType<typeof TestCases>;
@ -257,7 +257,7 @@ describe('common test suite', () => {
}
const tests = (peel(TestCases._.cases(peel(samples) as TestCases)) as
Dictionary<Value<DefaultPointer>, DefaultPointer>);
Dictionary<DefaultPointer>);
tests.forEach((t0: Value<DefaultPointer>, tName0: Value<DefaultPointer>) => {
const tName = Symbol.keyFor(strip(tName0) as symbol)!;
const t = peel(t0) as Record<symbol, any, DefaultPointer>;