Skip to content

Instantly share code, notes, and snippets.

@minoki
Created August 4, 2017 09:32
Show Gist options
  • Save minoki/9c0e7a5bd4efd889339c9931c1d51c76 to your computer and use it in GitHub Desktop.
Save minoki/9c0e7a5bd4efd889339c9931c1d51c76 to your computer and use it in GitHub Desktop.
TypeScriptの正規表現にマシな型をつける ref: http://qiita.com/mod_poppo/items/4549dcfa16ae99ebf126
let m = str.match(/(a)(b)?/);
if (m) {
let a: string = m[1];
let b: string = m[2]; // 本当は string | undefined
}
let str = "x\\yz";
let t = str.replace(/\\(.)/, (m, s) => s.toUpperCase());
// s は any 型なので、仮に s.toUpper() と書いてもエラーにならない
interface String {
match<captures extends SList>(regexp: TypedRegExp<captures>): TypedRegExpMatchArray<captures> | null;
replace<a0, a1, a2>(searchValue: TypedRegExp<SCons<a0, SCons<a1, SCons<a2, SNil>>>>, replacer: (match: string, a0: a0, a1: a1, a2: a2, offset: number, string: string) => string): string;
replace<a0, a1>(searchValue: TypedRegExp<SCons<a0, SCons<a1, SNil>>>, replacer: (match: string, a0: a0, a1: a1, offset: number, string: string) => string): string;
replace<a0>(searchValue: TypedRegExp<SCons<a0, SNil>>, replacer: (match: string, a0: a0, offset: number, string: string) => string): string;
replace(searchValue: TypedRegExp<SNil>, replacer: (match: string, offset: number, string: string) => string): string;
}
type SNil = {
"tag": "Nil";
};
type SCons<a, b extends SList> = {
"tag": "Cons";
"head": a;
"tail": b;
};
type SList = SNil | {
"tag": "Cons";
"head": any;
"tail": SList;
};
// type SList = SNil | SCons<any, SList>; とは書けないが、上のようには書ける
// リストの連結
type Concat<a extends SList, b extends SList> = {
"Nil": b;
"Cons": SCons<a["head"], Concat<a["tail"], b>>;
}[a["tag"]];
// リストの要素を全て undefined で置き換える
type FillUndefined<a extends SList> = {
"Nil": SNil;
"Cons": SCons<undefined, FillUndefined<a["tail"]>>;
}[a["tag"]];
// リストの要素に対して | undefined を行う
type MaybeUndefined<a extends SList> = {
"Nil": SNil;
"Cons": SCons<a["head"] | undefined, MaybeUndefined<a["tail"]>>;
}[a["tag"]];
// error TS2322: Type '0' is not assignable to type 'SCons<"a", SCons<"b", SCons<"c", SCons<"d", SNil>>>>'.
let x : Concat<SCons<"a", SCons<"b", SNil>>, SCons<"c", SCons<"d", SNil>>> = 0;
// error TS2322: Type '0' is not assignable to type 'SCons<undefined, SCons<undefined, SCons<undefined, SNil>>>'.
let y : FillUndefined<SCons<string, SCons<number, SCons<null, SNil>>>> = 0;
// error TS2322: Type '0' is not assignable to type 'SCons<string | undefined, SCons<number | undefined, SCons<null | undefined, SNil>>>'.
let z : MaybeUndefined<SCons<string, SCons<number, SCons<null, SNil>>>> = 0;
type ToTuple5<a0, a1, a2, a3, a4, a extends SList> = {
"Nil": [a0, a1, a2, a3, a4];
"Cons": [a0, a1, a2, a3, a4, a["head"]]; // a["tail"] is dropped...
}[a["tag"]];
type ToTuple4<a0, a1, a2, a3, a extends SList> = {
"Nil": [a0, a1, a2, a3];
"Cons": ToTuple5<a0, a1, a2, a3, a["head"], a["tail"]>;
}[a["tag"]];
type ToTuple3<a0, a1, a2, a extends SList> = {
"Nil": [a0, a1, a2];
"Cons": ToTuple4<a0, a1, a2, a["head"], a["tail"]>;
}[a["tag"]];
type ToTuple2<a0, a1, a extends SList> = {
"Nil": [a0, a1];
"Cons": ToTuple3<a0, a1, a["head"], a["tail"]>;
}[a["tag"]];
type ToTuple1<a0, a extends SList> = {
"Nil": [a0];
"Cons": ToTuple2<a0, a["head"], a["tail"]>;
}[a["tag"]];
type ToTuple<a extends SList> = {
"Nil": [];
"Cons": ToTuple1<a["head"], a["tail"]>;
}[a["tag"]];
type TypedRegExpExecArray<captures extends SList> = ToTuple<SCons<string, captures>> & {index: number; input: string};
type TypedRegExpMatchArray<captures extends SList> = ToTuple<SCons<string, captures>> & {index?: number; input?: string};
type ToFunction5<r, a0, a1, a2, a3, a4, a extends SList> = {
"Nil": (a0: a0, a1: a1, a2: a2, a3: a3, a4: a4) => r;
"Cons": (a0: a0, a1: a1, a2: a2, a3: a3, a4: a4, a5: a["head"]) => r; // a["tail"] is dropped...
}[a["tag"]];
type ToFunction4<r, a0, a1, a2, a3, a extends SList> = {
"Nil": (a0: a0, a1: a1, a2: a2, a3: a3) => r;
"Cons": ToFunction5<r, a0, a1, a2, a3, a["head"], a["tail"]>;
}[a["tag"]];
type ToFunction3<r, a0, a1, a2, a extends SList> = {
"Nil": (a0: a0, a1: a1, a2: a2) => r;
"Cons": ToFunction4<r, a0, a1, a2, a["head"], a["tail"]>;
}[a["tag"]];
type ToFunction2<r, a0, a1, a extends SList> = {
"Nil": (a0: a0, a1: a1) => r;
"Cons": ToFunction3<r, a0, a1, a["head"], a["tail"]>;
}[a["tag"]];
type ToFunction1<r, a0, a extends SList> = {
"Nil": (a0: a0) => r;
"Cons": ToFunction2<r, a0, a["head"], a["tail"]>;
}[a["tag"]];
type ToFunction<r, a extends SList> = {
"Nil": () => r;
"Cons": ToFunction1<r, a["head"], a["tail"]>;
}[a["tag"]];
// interface TypedRegExp<> extends RegExp では 'exec' の型が違うと言われて怒られた、ので交差型を使う
type TypedRegExp<captures extends SList> = {
exec(string: string): TypedRegExpExecArray<captures> | null;
} & RegExp; /* RegExp が & の後でないとメソッドの型を上書きできないようなので注意 */
interface String {
match<captures extends SList>(regexp: TypedRegExp<captures>): TypedRegExpMatchArray<captures> | null;
replace<captures extends SList>(searchValue: TypedRegExp<captures>, replacer: ToFunction<string, SCons<string, Concat<captures, SCons<number, SCons<string, SNil>>>>>): string;
}
let str = "abc"
let m = str.match(/(a)(b)?/ as TypedRegExp<SCons<string, SCons<string | undefined, SNil>>>);
if (m) {
let a: string = m[1];
let b: string = m[2]; // --strictNullChecks でエラー
}
let str = "x\\yz";
let t = str.replace(/\\(.)/ as TypedRegExp<SCons<string, SNil>>, (m, s) => s.toUpper()); // エラーにならない!
console.log(t);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment