diff --git a/src/services/inlayHints.ts b/src/services/inlayHints.ts index ce779052a05..a3e54cf2378 100644 --- a/src/services/inlayHints.ts +++ b/src/services/inlayHints.ts @@ -119,6 +119,7 @@ import { TupleTypeReference, Type, TypeFlags, + TypePredicate, unescapeLeadingUnderscores, UserPreferences, usingSingleLineStringWriter, @@ -405,6 +406,16 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] { return; } + const typePredicate = checker.getTypePredicateOfSignature(signature); + + if (typePredicate?.type) { + const hintParts = typePredicateToInlayHintParts(typePredicate); + if (hintParts) { + addTypeHints(hintParts, getTypeAnnotationPosition(decl)); + return; + } + } + const returnType = checker.getReturnTypeOfSignature(signature); if (isModuleReferenceType(returnType)) { return; @@ -474,6 +485,17 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] { }); } + function printTypePredicateInSingleLine(typePredicate: TypePredicate) { + const flags = NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.AllowUniqueESSymbolType | NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope; + const printer = createPrinterWithRemoveComments(); + + return usingSingleLineStringWriter(writer => { + const typePredicateNode = checker.typePredicateToTypePredicateNode(typePredicate, /*enclosingDeclaration*/ undefined, flags); + Debug.assertIsDefined(typePredicateNode, "should always get typePredicateNode"); + printer.writeNode(EmitHint.Unspecified, typePredicateNode, /*sourceFile*/ file, writer); + }); + } + function typeToInlayHintParts(type: Type): InlayHintDisplayPart[] | string { if (!shouldUseInteractiveInlayHints(preferences)) { return printTypeInSingleLine(type); @@ -481,10 +503,24 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] { const flags = NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.AllowUniqueESSymbolType | NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope; const typeNode = checker.typeToTypeNode(type, /*enclosingDeclaration*/ undefined, flags); - Debug.assertIsDefined(typeNode, "should always get typenode"); + Debug.assertIsDefined(typeNode, "should always get typeNode"); + return getInlayHintDisplayParts(typeNode); + } + function typePredicateToInlayHintParts(typePredicate: TypePredicate): InlayHintDisplayPart[] | string { + if (!shouldUseInteractiveInlayHints(preferences)) { + return printTypePredicateInSingleLine(typePredicate); + } + + const flags = NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.AllowUniqueESSymbolType | NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope; + const typeNode = checker.typePredicateToTypePredicateNode(typePredicate, /*enclosingDeclaration*/ undefined, flags); + Debug.assertIsDefined(typeNode, "should always get typenode"); + return getInlayHintDisplayParts(typeNode); + } + + function getInlayHintDisplayParts(node: Node) { const parts: InlayHintDisplayPart[] = []; - visitForDisplayParts(typeNode); + visitForDisplayParts(node); return parts; function visitForDisplayParts(node: Node) { diff --git a/tests/baselines/reference/inlayHintsInferredTypePredicate1.baseline b/tests/baselines/reference/inlayHintsInferredTypePredicate1.baseline new file mode 100644 index 00000000000..df607df5111 --- /dev/null +++ b/tests/baselines/reference/inlayHintsInferredTypePredicate1.baseline @@ -0,0 +1,9 @@ +// === Inlay Hints === +function test(x: unknown) { + ^ +{ + "text": ": x is number", + "position": 25, + "kind": "Type", + "whitespaceBefore": true +} \ No newline at end of file diff --git a/tests/baselines/reference/inlayHintsInteractiveInferredTypePredicate1.baseline b/tests/baselines/reference/inlayHintsInteractiveInferredTypePredicate1.baseline new file mode 100644 index 00000000000..7bc4345bf09 --- /dev/null +++ b/tests/baselines/reference/inlayHintsInteractiveInferredTypePredicate1.baseline @@ -0,0 +1,23 @@ +// === Inlay Hints === +function test(x: unknown) { + ^ +{ + "text": "", + "displayParts": [ + { + "text": ": " + }, + { + "text": "x" + }, + { + "text": " is " + }, + { + "text": "number" + } + ], + "position": 25, + "kind": "Type", + "whitespaceBefore": true +} \ No newline at end of file diff --git a/tests/cases/fourslash/inlayHintsInferredTypePredicate1.ts b/tests/cases/fourslash/inlayHintsInferredTypePredicate1.ts new file mode 100644 index 00000000000..fd1de15cd9f --- /dev/null +++ b/tests/cases/fourslash/inlayHintsInferredTypePredicate1.ts @@ -0,0 +1,11 @@ +/// + +// @strict: true + +//// function test(x: unknown) { +//// return typeof x === 'number'; +//// } + +verify.baselineInlayHints(undefined, { + includeInlayFunctionLikeReturnTypeHints: true, +}); diff --git a/tests/cases/fourslash/inlayHintsInteractiveInferredTypePredicate1.ts b/tests/cases/fourslash/inlayHintsInteractiveInferredTypePredicate1.ts new file mode 100644 index 00000000000..88282cf849e --- /dev/null +++ b/tests/cases/fourslash/inlayHintsInteractiveInferredTypePredicate1.ts @@ -0,0 +1,12 @@ +/// + +// @strict: true + +//// function test(x: unknown) { +//// return typeof x === 'number'; +//// } + +verify.baselineInlayHints(undefined, { + interactiveInlayHints: true, + includeInlayFunctionLikeReturnTypeHints: true, +}); \ No newline at end of file