TypeScriptでオーバーロード実装された関数を使う関数を書いていたら 「この呼び出しに一致するオーバーロードはありません。」TS(2769) と言われた

英語だと 'No overload matches this call' って言われると思う。タイトルが長い。

TS書いてて、関数の引数に Options なオブジェクトを受け取って、そのオプションのプロパティによって返り値が違うという関数を書いていた(hoge関数とする)。

で、hoge関数を使いつつ、hoge関数が受け取れる引数の型を外部から渡せる関数(foo関数とする)を書いていた。←ここでタイトルのエラーで怒られてしまった。

コード

type A = {
    passNumber: number
    toReturnString?: undefined
}

type B = {
    passNumber: number
    toReturnString: true
}

type Options = A | B

function hoge(options: A): number
function hoge(options: B): string
// function hoge(options: Options): string | number
function hoge(options: Options) {
    if(options.toReturnString === true) {
        return options.passNumber.toString()
    } else {
        return options.passNumber
    }
}

// 普通に使う分には通る
const willNumber = hoge({passNumber:1})
const willString = hoge({passNumber:1,toReturnString:true})

console.log('willNumber',willNumber)
console.log('willString', willString)

// 通らない!!!!!!!!!!
function foo(options:Options) {
    return hoge(options)
}

Playground Link

コードの解説

上記コードブロックのhoge関数は toReturnString がoptionalで、trueだったら返り値がstringになるし、そうじゃなかったらnumberで返るという関数。

hoge関数をだけで使う分には通るのだけど、foo関数でhoge関数にargmentsで受け取ったオプションの同じ型を渡すとエラーになる(L33)。

答え

上記TS playgroundのL15のコメントアウト箇所を外せば通るようになる。

具体的にはhoge関数のオーバーロード記述の3つ目の行

function hoge(options: Options): string | number

感想

オーバーロードのimplements(実装記述)で書いていれば通るものだと思っていたのでハマってしまった。

オーバーロードの対応する型パターンは全部記述しないといけないらしいという学びがあった。

最初、hoge関数単体で使えばコンパイル通るしちゃんと返り値が意図通りのオーバーロードの挙動をしてくれていたのでオーバーロード実装側に問題があると気づけなかった。foo関数の実装側でなにか凡ミスがあるのかとウンウン唸って時間を使ってしまった。

yarn workspacesのnoHoistがうまくされないときあるいはnoHoist設定の罠

yarn workspacesでmonorepoを構成しているときに特定のpackageだけhoistの対象外にしたいというときがある。

そういうときにどうすればよいかというと、noHoistオプションを単に使えばいいのだけどなぜかちゃんと書いてあるつもりなのにnoHoistが効かなくて困り果ててしまうケースがある。

このnoHoist設定は実はめちゃくちゃややこしく罠しかないので覚書として書いておく。

nohoist in Workspaces | Yarn Blog

結論から

noHoistにはディレクトリパスではなくパッケージ名のglobを書かねばならない

です。

それでは順を追って見ていきましょう。

packageの名は

こういうmonorepo構成だったとする。このうちfun-utilsだけはhoistの対象にしたくないのでnoHoistしたいとする。

.
├─package.json
└─packages
  ├─fun-domain
  ├─fun-interfaces
  └─fun-utils // hoistしてほしくない

そしてpackages/fun-utilspackage.jsonはどうなっているかというとこう。

{
  "name": "smile-utils",
  "version": "1.0.0",

  "dependencies": {
    "cross-env": "5.0.5"
  }
}

おや?ディレクトリはpackages/fun-utilsなのにpackgae.jsonnameフィールドは「smile-utils」ですね。

でもまあpackagesのフィールドはディレクトリなわけだし、普通こう書いてしまいますね。

"workspaces": {
  "packages": ["packages/*"],
  "nohoist": ["**/fun-utils", "**/fun-utils/**"]
}

noHoistにディレクトリ名ベースのglobを書いた

ただこれではnoHoistの対象になりません

どうすればよいか。

"workspaces": {
  "packages": ["packages/*"],
  "nohoist": ["**/smile-utils", "**/smile-utils/**"]
}

nameフィールドのglobに変えた

noHoistは実はpackage.jsonnameのglobにしないといけなかったのです……。

ちなみに

1度rootで yarn install してあり、すでに各パッケージにlockファイルとnode_modules存在する状態からnoHoistの指定する場合は、noHoist対象node_modulesyarn.lockを削除しておかないといけないことを確認しています(v1.22.4)

yarn install --force でもダメなので注意しましょう……。


😇