generated at
なぜ型定義で実際に返される型のUnion Typeになっていないのか

前提
Discord.jsの client.channels.get(id) は本来、 TextChannel DMChannel などの実際に存在する対象を返す
ただ型定義上は、チャンネルという共通概念である Channel を返すようになっている
ドキュメント上でも同じようになっているけど、これは普通に書き換えたほうがわかりやすい気がする
実際には TextChannel であっても、型定義で Channel になっていると、型エラーが出てしまって面倒くさい
TypeScriptを使っていると通常ではなるべく避ける as を使うはめになって、型安全か冗長化を選ばざるを得なくなる

なぜそうなっているのか
調査はしていないけど、ドキュメント上も Channel を返すようになっているのを踏まえると昔からの成り行きっぽい
問題が浮き彫りにならなかったとか、ドキュメント上の型もなんとなく理解できてたとかで
ただ少し前にUnion Typeに変えるPRが出されて実際にmergeされた (discord.js#3918)
一見問題なさそうに見えたけど、Union Typeのままでメソッド呼び出しができないという問題が発覚してrevertされた
TypeScriptの型推論上の問題として、Union Typeのままだとメソッド呼び出しができなくなるという問題があった
この型推論の挙動はTypeScript 4.0で改善されそうな気配があって、そのうちまたUnion Typeになるかもしれない
なったtig

検証
新たなバージョンがリリースされる前にrevertされて、その変更を含まれたバージョンが存在しない
代わりにGitHubから該当コミットをインストールすることで体験できる: discordjs/discord.js#69d69f2
条件式の中で型が絞り込めるのがわかる(上が通常のバージョンで、下がUnion Typeあり)

挙動の深堀り
ただ実際に検証してみるとsetNameではなくthenで型エラーが出てることから単にメソッド呼び出しが問題ではなくてもっと複雑な因子が絡んでいそうな感じがした
channel変数は TextChannel | VoiceChannel のような、GuildChannelを継承したクラスのUnion Typeになっていて、それらのクラスにはsetNameが定義されていなく、GuildChannel.setNameという扱いになってる可能性はありそう
つまりUnion Typeでも、それらの共通の親クラスのメソッドとして解決できると対象外となる...?
そしてsetNameを抜けてPromise付きで Promise<TextChannel> | Promise<VoiceChannel> のような各クラスのUnion Typeになって、それぞれ独立した型に型に対するメソッド呼び出しで型エラーが出るといった感じっぽい

回避策
今の段階だと型を補完するために、自分で明示する必要があって、上にも書いたように as を使う方法がある
ただ闇雲に as を使うと間違った型に変えかねないから、多少冗長にはなってしまうがType Predicateのほうが安全
channel.type でチャンネルの種類が取得できるのでこのように書ける(もちろんinstanceofを使ってもよい)
NewsChannelなどはTextChannelを継承しているけどtypeは"news"なのでinstanceofではなくてtypeを使ったほうがよい
ts
const isTextChannel = (channel: Discord.Channel): channel is Discord.TextChannel => channel.type === 'text'

関連