Reputation: 7633
In my typescript program I have two coordinate systems, that are convertible to each-other. They are both x,y but the have different scales. I would like to setup typescript so that it will warn me if I pass the wrong type to a function.
type Vec2 = [number,number]; // the base type
interface TileC extends Vec2 {}; // the two types I would like to be exclusive
interface ChunkC extends Vec2 {}; // the two types I would like to be exclusive
// example functions
let chunkSize = 32;
function tileToChunk(t: TileC): ChunkC {
const [x,y] = t;
const c: Vec2 = [Math.floor(x/chunkSize), Math.floor(y/chunkSize)];
return c;
}
function chunkToTile(c: ChunkC): TileC {
const [cx,cy] = c;
return [cx*chunkSize, cy*chunkSize];
}
I would like the following to be an error
let chunkCoord: ChunkC = tileToChunk([7,14]);
let wantError = tileToChunk(chunkCoord);
As it stands, this compiles just fine. Am I missing a compiler option?
Upvotes: 1
Views: 170
Reputation: 7633
Please see my update in the accepted answer.
After taking a further look into this. I can achieve what I want by doing this.
Note that _type_lock
can be any name, as long as it's the same name for both (all) similar types. "foo"
and "bar"
can be anything as long as they are unique. The name of the type is probably a good option here.
type Vec2 = [number,number]; // the base type
interface TileC extends Vec2 {
_type_lock?: "foo";
};
interface ChunkC extends Vec2 {
_type_lock?: "bar";
};
Upvotes: 1
Reputation: 7633
After more testing, my initial answer actually doesn't work. This version works. Features:
code:
// base type
type Vec2 = [number,number];
// lock helpers
interface LockTileC {
readonly _typelock_tilec?: void;
}
interface LockChunkC {
readonly _typelock_chunkc?: void;
}
// instanced types
type TileC = [number,number,LockTileC?];
type ChunkC = [number,number,LockChunkC?];
// This option does it all in one line, but the error messages are worse
// type TileC = [number,number,{readonly _typelock_tilec?: void;}?];
// type ChunkC = [number,number,{readonly _typelock_chunkc?: void;}?];
function baseType(num: Vec2): void {
console.log(`${num[0]}, ${num[1]}`);
}
function onlyTileC(num: TileC): void {
console.log(`${num[0]}, ${num[1]}`);
}
function onlyChunkC(num: ChunkC): void {
console.log(`${num[0]}, ${num[1]}`);
}
function example(): void {
let asChunk: ChunkC = [1,2];
let asTile: TileC = [32,64];
let base: Vec2 = [3,4];
onlyTileC(asTile); // OK: type to type
onlyTileC(asChunk); // FAIL: cross type to cross type
onlyChunkC(asTile); // FAIL: cross type to cross type
onlyTileC(base); // OK: base to type
onlyChunkC(base); // OK: base to type
baseType(asChunk); // FAIL: type to base
baseType(<Vec2>asChunk); // OK: type to base with casting
}
Upvotes: 0
Reputation: 2828
You are not missing a compiler option, this is by design. TypeScript compiles to JavaScript and hence there is no static type information available at runtime.
TypeScript uses structural typing:
The basic rule for TypeScript’s structural type system is that x is compatible with y if y has at least the same members as x.
https://www.typescriptlang.org/docs/handbook/type-compatibility.html
Good news is you can use Discriminated Unions to achieve your desired behavior, for example by adding a kind
property:
interface TileC {
x: number;
y: number;
kind: "Tile"
}
interface ChunkC {
x: number;
y: number;
kind: "Chunk"
}
interface VecC = TileC | ChunkC
Upvotes: 1