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; return this.includeAnnotations ? new Annotated(v) : v;
} }
static dictionaryFromArray<T>(vs: Value<T>[]): Dictionary<Value<T>, T> { static dictionaryFromArray<T>(vs: Value<T>[]): Dictionary<T> {
const d = new Dictionary<Value<T>, T>(); const d = new Dictionary<T>();
if (vs.length % 2) throw new DecodeError("Missing dictionary value"); if (vs.length % 2) throw new DecodeError("Missing dictionary value");
for (let i = 0; i < vs.length; i += 2) { for (let i = 0; i < vs.length; i += 2) {
d.set(vs[i], vs[i+1]); 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> { export class Dictionary<T = DefaultPointer, V = Value<T>> extends KeyedDictionary<Value<T>, V, T> {
static isDictionary<V, T = DefaultPointer>(x: any): x is Dictionary<V, T> { static isDictionary<T = DefaultPointer, V = Value<T>>(x: any): x is Dictionary<T, V> {
return x?.[DictionaryType] === 'Dictionary'; return x?.[DictionaryType] === 'Dictionary';
} }
static fromJS<V = DefaultPointer, T = DefaultPointer>(x: object): Dictionary<Value<V>, T> { static fromJS<T = DefaultPointer, V = DefaultPointer>(x: object): Dictionary<T, Value<V>> {
if (Dictionary.isDictionary<Value<V>, T>(x)) return x as Dictionary<Value<V>, T>; if (Dictionary.isDictionary<T, Value<V>>(x)) return x;
const d = new Dictionary<Value<V>, T>(); const d = new Dictionary<T, Value<V>>();
Object.entries(x).forEach(([key, value]) => d.set(key, fromJS(value))); Object.entries(x).forEach(([key, value]) => d.set(key, fromJS(value)));
return d; 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; record(r: Record<Value<T>, Tuple<Value<T>>, T>, k: Fold<T, R>): R;
array(a: Array<Value<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; 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; 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> { set(s: Set<T>, k: Fold<T, Value<R>>): Value<R> {
return s.map(k); 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)]); return d.mapEntries(([key, value]) => [k(key), k(value)]);
} }
annotated(a: Annotated<T>, k: Fold<T, Value<R>>): Value<R> { 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); return o.array(v, walk);
} else if (Set.isSet<T>(v)) { } else if (Set.isSet<T>(v)) {
return o.set(v, walk); return o.set(v, walk);
} else if (Dictionary.isDictionary<Value<T>, T>(v)) { } else if (Dictionary.isDictionary<T>(v)) {
return o.dictionary(v, walk); return o.dictionary(v, walk);
} else if (Annotated.isAnnotated<T>(v)) { } else if (Annotated.isAnnotated<T>(v)) {
return o.annotated(v, walk); 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; }, array(_a: Array<Value<T>>, _k: Fold<T, boolean>): boolean { return false; },
set(_s: Set<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; return false;
}, },

View File

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

View File

@ -26,13 +26,20 @@ export interface RecordConstructorInfo<L extends Value<T>, T = DefaultPointer> {
arity: number; 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>>( export function Record<L, FieldsType extends Tuple<any>>(
label: L, fields: FieldsType): label: L,
L extends Value<infer T> fields: FieldsType): InferredRecordType<L, FieldsType>
? (FieldsType extends Tuple<Value<T>>
? Record<L, FieldsType, T>
: never)
: never
{ {
(fields as any).label = label; (fields as any).label = label;
return fields as any; return fields as any;

View File

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

View File

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