Reputation: 15
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");
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
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 marker
evaluates 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
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