Open9

arg is Tの推論を目指す

uhyouhyo

やりたいこと: contextual typeがある場合に関数式の返り値の型としてarg is Tが推論される。
現在の挙動: contextual typeの返り値型がarg is Tだとしても無視されて boolean になる。

  • contextuallyCheckFunctionExpressionOrObjectLiteralMethod に関数式のチェックがある
uhyouhyo
  • interface Signatureの定義を見ると resolvedReturnType とは別に resolvedTypePredicate が存在している
uhyouhyo
  • getContextualSignaturegetApparentTypeOfContextualType ここで contextual type が来ているけどSignatureを復元できるか?
    • よく考ええたら関数は callSignatures: readonly Signature[] を持つオブジェクト型だからまあできる
    • console.logしてみたらresolvedTypePredicate を持つ型が contextualSignature として得られていた
    • instantiatedContextualSignature にも引き継がれている
uhyouhyo

contextuallyCheckFunctionExpressionOrObjectLiteralMethod 内で contextualSignature から推論された関数型へtype predicateを引き継がせるコード。(これに対する推論が何もないので型変数がリークしたりする)

                    const contexntualSignatureTypePredicate = getTypePredicateOfSignature(contextualSignature);
                    if (!signature.resolvedTypePredicate && contexntualSignatureTypePredicate) {
                        signature.resolvedTypePredicate = contexntualSignatureTypePredicate;
                    }
uhyouhyo

型変数に推論を働かせるための手がかり: この辺りを追うとよさそう

resolveCallchooseOverloadcreateInferenceContextcreateInferenceContextWorkermakeFixingMapperForContextgetInfereedTypeinstantiateType

uhyouhyo
const nums = [1, 2, null, 4, 5, null, 7, 8];
const filtered = nums.filter(x => x !== null);
const a: number[] = filtered;

この時点で inferTypeArguments により number | null が推論されているがそれが反映されず S のままになっている。

uhyouhyo
function id <S>(func: (arg: unknown) => arg is S): ((arg: unknown) => arg is S) {return func}
const func = id((x) => typeof x === 'number');

これのfuncに対応するSignatureを調べると、resolvedTypePredicatearg is S が入っているがSignatureが targetmapper を持たない。

  • getResolvedSignature が返したSignatureはmapperとして S => Sを持っているように見える。ここがおかしい。(本来は S => unknown のように型変数が解決されているべき)
uhyouhyo
  • interfaceContext.inferences[0].candidatesSがそのまま入るタイミングがある。
    • inferTypeArgumentsinferTypes (argに対するforループのところ) → inferFromTypesinferFromObjectTypesinferFromSignatures(source, target, SignatureKind.Call);

      • source = (x) => x is S
      • target = (arg) => arg is S
    • 最終的に inferFromTypes( <S>, <S> )になってここで S = S が生成される。これが正しい挙動かどうか要調査

uhyouhyo

同じような推論過程になると期待される次のコードの場合はどうなるか?

function id<S>(func: (arg: S) => S): (arg: S) => S { return func }
const func = id((x) => x);

→より早いタイミングでsourceが unknown に解決されている

  • inferTypeArguments から呼び出される checkExpressionWithContextualType の返り値の時点ですでに (x) => unknown になっている
  • 関数引数の xsymbol.links.type にすでに unknownが入っている。どこから?