0
votes

TypeScript Version: 2.6.0-dev.20170826 and 2.4.2

I wonder if I hit some typescript inference bug/limitation, or if my code is simply incorrect. If the code is actually valid, and it's a type inference issue, I'll report a bug to the typescript github.

I'm trying to constrain the Set builder to accept only types for which equality is properly defined (to avoid this problem).

strange.d.ts

declare module 'strange' {
    export type WithEquality = string|number|boolean|{equals(other: any): boolean; hashCode(): number;};

    export interface Set<T> {
        add(other: T): Set<T>;
    }

    export function makeSetUnsafe<V>(...vals: V[]): Set<V>;
    export function makeSet<V extends WithEquality>(...vals: V[]): Set<V>;
}

strange.ts

///<reference path="./strange.d.ts"/>

import * as S from 'strange';

const x = S.makeSetUnsafe(1,2,3);
x.add(4);

const y = S.makeSet(1,2,3);
y.add(4);

Expected behavior: From my understanding the code should compile. Typescript should infer number for both examples as the type, and since number is an option in WithEquality, and the constraint is T extends WithEquality, all should be fine.

Actual behavior: The call to makeSetUnsafe compiles fine, but the call to makeSet fails with this error:

strange.ts(9,7): error TS2345: Argument of type '4' is not assignable to parameter of type '1 | 2 | 3'.

Adding the generic constraint to check that the generic type of the Set interface extends WithEquality causes the inference to pick 1|2|3 instead of number for T.

Giving the type explicitly with const y = S.makeSet<number>(1,2,3); makes it build, so it seems adding the generic constraint makes the type inference pick another type.

In fact I can reproduce the issue even with the simpler

export type WithEquality = number;

A good answer would explain why this code is wrong, hopefully give a typescript implementation allowing to express these constraints with the type inference working, or confirm that it's a typescript limitation.

1

1 Answers

2
votes

As for why that happens, check the answer to this issue:
Type variable constrained to primitive or union type not assignable to it.

You can fix it like this:

export function makeSet<V>(...vals: Array<V & WithEquality>): Set<V>;

And then:

const y = makeSet2(1,2,3);
y.add(4); // fine
const z = makeSet(/a*/); // error