Imtnt
Imtnt

Reputation: 15

Typescript no error on generic type mismatch

I'm attempting to build a type safe attachment system, but typescript doesn't seem to throw an error when the types mismatch in a generic function call

// Generic key that holds value type info
type GenericTypeMarker<T> = string;

// Standard dictionarny
const dictionary: { [id: string]: any } = {};

// Put item into dict with type specified by key
function putItem<T>(key: GenericTypeMarker<T>, item: T) {
    dictionary[key] = item;
}

// Get item from dict with type specified by key
function getItem<T>(key: GenericTypeMarker<T>): T {
    return dictionary[key];
}


// A test key with type of number
const TestKey: GenericTypeMarker<number> = "testKey";

// type mismatch between value and key, but no error
putItem(TestKey, "not a string");

TS Playground link

I don't see why the last line doesn't throw a compiler error when the type of the key and value don't line up.

EDIT: per jcalz comment, I understand why this is the case. However, is there a way around this that maintains type safety?

Upvotes: 0

Views: 1014

Answers (2)

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 249506

As others have mentioned if you don't use the type parameter it has little effect on compatibility, so the simplest solution is to use it. The most benign way to use it is to intersect string with a type that contains a dummy property, (similar to branded types)

// Generic key that holds value type info
type GenericTypeMarker<T> = string & { markerHelper: T };

// Standard dictionarny
const dictionary: { [id: string]: any } = {};

// Put item into dict with type specified by key
function putItem<T>(key: GenericTypeMarker<T>, item: T) {
    dictionary[key] = item;
}

function makeMarker<T>(value : string) : GenericTypeMarker<T>{
  return value as any
}
// create the key with an any assertion 
const TestKey: GenericTypeMarker<number> = "testKey" as any;
// or using the helper
const TestKey2 =  makeMarker<number>("testKey");

putItem(TestKey,2); // ok
putItem(TestKey,"2");// error

The disadvantage is someone might try to access TestKey.marker and be surprised that there is no value. One workaround is to use T & never for the marker type which although when you try to access markerevaluates to never still works for the purpose of infering T in putItems for example:

type GenericTypeMarker<T> = string & { marker: T & never};

const TestKey =  makeMarker<number>("testKey");
let d = TestKey.marker; // d is never
putItem(TestKey,2); // ok
putItem(TestKey,"2");// error

Upvotes: 1

Huy Nguyen
Huy Nguyen

Reputation: 3010

The reason is that your generic type GenericTypeMarker doesn't use the type parameter T. As of TypeScript 2.8, the compiler can flag that as an error.

What you want is called literal types in Flow. There have been requests to add it to TypeScript.

Upvotes: 1

Related Questions