addという関数があり、型Aの値同士を足せば型Aが返り、型Bの値同士を足せば形Bが返る、と宣言した test.d.ts
export function add(x: A, y: A): A;
export function add(x: B, y: B): B;
しかし実際に使うとB同士を足した結果がAになってしまった、なぜか?
問題を再現する最小限のコード全体 test.ts
import { A, B, add } from "./testlib.js";
let x = new B();
let y = new B();
let z = add(x, y); // type of z is A
console.log(z);
testlib.d.ts
export class A {}
export class B {}
export function add(x: A, y: A): A;
export function add(x: B, y: B): B;
testlib.js
class A{ }
class B{ }
function add(x, y) {
return x + y;
}
export {A, B, add}
解説
- TypeScriptは名前的型システムではなく構造的型システム
- なので構造が一致する型は同じ型
- AとBは同じ構造なので同じ型
- なので先に出現した
export function add(x: A, y: A): A;
のルールが使われ、返り値がAとなる
AとBが区別されていないのでAとBをaddしてもエラーにならない :
let ab = add(new A(), new B()); // No error
解決方法 testlib.d.ts
export class A {
_A_Brand: never;
}
export class B {
_B_Brand: never;
}
これでAとBが異なる構造になるので型として区別される
この例では2つのクラスを区別したいシチュエーションだったので「使わないメンバーをつける」という選択をした。
文字列型を区別したいケースではメンバーを追加できないのでこの方法は使えない。
別の方法としてenumとのintersectionを作る方法がある。この場合は文字列型をFooId型にするのがas FooId
でできる。
ts
enum FooIdBrand { _ = "" };
type FooId = FooIdBrand & string;
const fooId = 'foo' as FooId;
関連