From 9c9a92f31b6e07b596bc5481929dfe1e57ca36a1 Mon Sep 17 00:00:00 2001 From: syi0808 Date: Sun, 21 Jul 2024 02:11:03 +0900 Subject: [PATCH 1/7] fix(completions): filter promise properties in object expression --- src/compiler/checker.ts | 2 ++ src/compiler/types.ts | 3 +++ src/services/completions.ts | 5 +++-- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 86702764c9585..ce8ec90b58c3f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1637,6 +1637,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { getParameterIdentifierInfoAtPosition, getPromisedTypeOfPromise, getAwaitedType: type => getAwaitedType(type), + getAwaitedTypeOfPromise: type => getAwaitedTypeOfPromise(type), getReturnTypeOfSignature, isNullableType, getNullableType, @@ -1861,6 +1862,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const node = getParseTreeNode(nodeIn); return node && tryGetThisTypeAt(node, includeGlobalThis, container); }, + filterType, getTypeArgumentConstraint: nodeIn => { const node = getParseTreeNode(nodeIn, isTypeNode); return node && getTypeArgumentConstraint(node); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 9e2a0b980ddae..eb4c2cbd0a5ec 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4973,6 +4973,7 @@ export interface TypeChecker { getPromisedTypeOfPromise(promise: Type, errorNode?: Node): Type | undefined; /** @internal */ getAwaitedType(type: Type): Type | undefined; + getAwaitedTypeOfPromise(type: Type): Type | undefined; /** @internal */ isEmptyAnonymousObjectType(type: Type): boolean; getReturnTypeOfSignature(signature: Signature): Type; @@ -5288,6 +5289,8 @@ export interface TypeChecker { tryGetThisTypeAt(node: Node, includeGlobalThis?: boolean, container?: ThisContainer): Type | undefined; /** @internal */ getTypeArgumentConstraint(node: TypeNode): Type | undefined; + filterType(type: Type, f: (t: Type) => boolean): Type; + /** * Does *not* get *all* suggestion diagnostics, just the ones that were convenient to report in the checker. * Others are added in computeSuggestionDiagnostics. diff --git a/src/services/completions.ts b/src/services/completions.ts index d240cacc2c3c3..3a1240a69d0d2 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -5472,9 +5472,10 @@ function getJsDocTagAtPosition(node: Node, position: number): JSDocTag | undefin /** @internal */ export function getPropertiesForObjectExpression(contextualType: Type, completionsType: Type | undefined, obj: ObjectLiteralExpression | JsxAttributes, checker: TypeChecker): Symbol[] { const hasCompletionsType = completionsType && completionsType !== contextualType; + const promiseFilteredContextualType = checker.filterType(contextualType, t => !checker.getAwaitedTypeOfPromise(t)); const type = hasCompletionsType && !(completionsType.flags & TypeFlags.AnyOrUnknown) - ? checker.getUnionType([contextualType, completionsType]) - : contextualType; + ? checker.getUnionType([promiseFilteredContextualType, completionsType]) + : promiseFilteredContextualType; const properties = getApparentProperties(type, obj, checker); return type.isClass() && containsNonPublicProperties(properties) ? [] : From c243c9c2093550d4e067c0b2ece4734d3acf7f2a Mon Sep 17 00:00:00 2001 From: syi0808 Date: Sun, 21 Jul 2024 02:20:39 +0900 Subject: [PATCH 2/7] test: add test --- ...mpletionsPropertiesWithPromiseUnionType.ts | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 tests/cases/fourslash/completionsPropertiesWithPromiseUnionType.ts diff --git a/tests/cases/fourslash/completionsPropertiesWithPromiseUnionType.ts b/tests/cases/fourslash/completionsPropertiesWithPromiseUnionType.ts new file mode 100644 index 0000000000000..529160b6589b9 --- /dev/null +++ b/tests/cases/fourslash/completionsPropertiesWithPromiseUnionType.ts @@ -0,0 +1,24 @@ +/// +// @strict: true + +//// type MyType = { +//// foo: string; +//// }; + +//// function fakeTest(cb: () => MyType | Promise) {} + +//// fakeTest(() => { +//// return { +//// /*a*/ +//// }; +//// }); + + +verify.completions( + { + marker: ['a'], + exact: [ + { name: 'foo', kind: 'property' }, + ] + } +); \ No newline at end of file From 994cc76ad388d915791ba03fd7ab7a0f9600079f Mon Sep 17 00:00:00 2001 From: syi0808 Date: Mon, 22 Jul 2024 01:04:44 +0900 Subject: [PATCH 3/7] test: accept baseline for new api(filterType, getAwaitedTypeOfPromise) --- tests/baselines/reference/api/typescript.d.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index fecf389b0c2c1..c09d4a3ba53bb 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -6070,6 +6070,7 @@ declare namespace ts { getBaseTypes(type: InterfaceType): BaseType[]; getBaseTypeOfLiteralType(type: Type): Type; getWidenedType(type: Type): Type; + getAwaitedTypeOfPromise(type: Type): Type | undefined; getReturnTypeOfSignature(signature: Signature): Type; getNullableType(type: Type, flags: TypeFlags): Type; getNonNullableType(type: Type): Type; @@ -6217,6 +6218,7 @@ declare namespace ts { isArrayLikeType(type: Type): boolean; resolveName(name: string, location: Node | undefined, meaning: SymbolFlags, excludeGlobals: boolean): Symbol | undefined; getTypePredicateOfSignature(signature: Signature): TypePredicate | undefined; + filterType(type: Type, f: (t: Type) => boolean): Type; /** * Depending on the operation performed, it may be appropriate to throw away the checker * if the cancellation token is triggered. Typically, if it is used for error checking From 48423f60c94e902cee0ea35be01aea4e34273ec1 Mon Sep 17 00:00:00 2001 From: syi0808 Date: Mon, 22 Jul 2024 11:08:49 +0900 Subject: [PATCH 4/7] fix: filter promise in union type internally --- src/compiler/checker.ts | 4 +--- src/compiler/types.ts | 3 --- src/services/completions.ts | 2 +- tests/baselines/reference/api/typescript.d.ts | 2 -- 4 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ce8ec90b58c3f..eeecf4cf3b080 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1637,7 +1637,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { getParameterIdentifierInfoAtPosition, getPromisedTypeOfPromise, getAwaitedType: type => getAwaitedType(type), - getAwaitedTypeOfPromise: type => getAwaitedTypeOfPromise(type), getReturnTypeOfSignature, isNullableType, getNullableType, @@ -1862,7 +1861,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const node = getParseTreeNode(nodeIn); return node && tryGetThisTypeAt(node, includeGlobalThis, container); }, - filterType, getTypeArgumentConstraint: nodeIn => { const node = getParseTreeNode(nodeIn, isTypeNode); return node && getTypeArgumentConstraint(node); @@ -42303,7 +42301,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { * * This is used to reflect the runtime behavior of the `await` keyword. */ - function getAwaitedType(type: Type, errorNode?: Node, diagnosticMessage?: DiagnosticMessage, ...args: DiagnosticArguments): Type | undefined { + function getAwaitedType(type: Type, errorNode?: Node, diagnosticMessage?: DiagnosticMessage, ...args: DiagnosticArguments): Type | undefined { const awaitedType = getAwaitedTypeNoAlias(type, errorNode, diagnosticMessage, ...args); return awaitedType && createAwaitedTypeIfNeeded(awaitedType); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index eb4c2cbd0a5ec..9e2a0b980ddae 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4973,7 +4973,6 @@ export interface TypeChecker { getPromisedTypeOfPromise(promise: Type, errorNode?: Node): Type | undefined; /** @internal */ getAwaitedType(type: Type): Type | undefined; - getAwaitedTypeOfPromise(type: Type): Type | undefined; /** @internal */ isEmptyAnonymousObjectType(type: Type): boolean; getReturnTypeOfSignature(signature: Signature): Type; @@ -5289,8 +5288,6 @@ export interface TypeChecker { tryGetThisTypeAt(node: Node, includeGlobalThis?: boolean, container?: ThisContainer): Type | undefined; /** @internal */ getTypeArgumentConstraint(node: TypeNode): Type | undefined; - filterType(type: Type, f: (t: Type) => boolean): Type; - /** * Does *not* get *all* suggestion diagnostics, just the ones that were convenient to report in the checker. * Others are added in computeSuggestionDiagnostics. diff --git a/src/services/completions.ts b/src/services/completions.ts index 3a1240a69d0d2..206d8a9e72d74 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -5472,7 +5472,7 @@ function getJsDocTagAtPosition(node: Node, position: number): JSDocTag | undefin /** @internal */ export function getPropertiesForObjectExpression(contextualType: Type, completionsType: Type | undefined, obj: ObjectLiteralExpression | JsxAttributes, checker: TypeChecker): Symbol[] { const hasCompletionsType = completionsType && completionsType !== contextualType; - const promiseFilteredContextualType = checker.filterType(contextualType, t => !checker.getAwaitedTypeOfPromise(t)); + const promiseFilteredContextualType = checker.getUnionType(filter(contextualType.flags & TypeFlags.Union ? (contextualType as UnionType).types : [contextualType], t => !checker.getAwaitedType(t) || !checker.getPromisedTypeOfPromise(t))); const type = hasCompletionsType && !(completionsType.flags & TypeFlags.AnyOrUnknown) ? checker.getUnionType([promiseFilteredContextualType, completionsType]) : promiseFilteredContextualType; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index c09d4a3ba53bb..fecf389b0c2c1 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -6070,7 +6070,6 @@ declare namespace ts { getBaseTypes(type: InterfaceType): BaseType[]; getBaseTypeOfLiteralType(type: Type): Type; getWidenedType(type: Type): Type; - getAwaitedTypeOfPromise(type: Type): Type | undefined; getReturnTypeOfSignature(signature: Signature): Type; getNullableType(type: Type, flags: TypeFlags): Type; getNonNullableType(type: Type): Type; @@ -6218,7 +6217,6 @@ declare namespace ts { isArrayLikeType(type: Type): boolean; resolveName(name: string, location: Node | undefined, meaning: SymbolFlags, excludeGlobals: boolean): Symbol | undefined; getTypePredicateOfSignature(signature: Signature): TypePredicate | undefined; - filterType(type: Type, f: (t: Type) => boolean): Type; /** * Depending on the operation performed, it may be appropriate to throw away the checker * if the cancellation token is triggered. Typically, if it is used for error checking From 84792c85eea10a215fc3d7130652b5631fb52f75 Mon Sep 17 00:00:00 2001 From: syi0808 Date: Mon, 22 Jul 2024 11:44:47 +0900 Subject: [PATCH 5/7] formatting --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index eeecf4cf3b080..86702764c9585 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -42301,7 +42301,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { * * This is used to reflect the runtime behavior of the `await` keyword. */ - function getAwaitedType(type: Type, errorNode?: Node, diagnosticMessage?: DiagnosticMessage, ...args: DiagnosticArguments): Type | undefined { + function getAwaitedType(type: Type, errorNode?: Node, diagnosticMessage?: DiagnosticMessage, ...args: DiagnosticArguments): Type | undefined { const awaitedType = getAwaitedTypeNoAlias(type, errorNode, diagnosticMessage, ...args); return awaitedType && createAwaitedTypeIfNeeded(awaitedType); } From 80a9e22de687bb1f6b9793b7b6a2f1c903205733 Mon Sep 17 00:00:00 2001 From: syi0808 Date: Fri, 9 Aug 2024 09:00:49 +0900 Subject: [PATCH 6/7] fix: remove getAwaitedType from filter --- src/services/completions.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/services/completions.ts b/src/services/completions.ts index 206d8a9e72d74..6ffd73dcb3367 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -5472,7 +5472,13 @@ function getJsDocTagAtPosition(node: Node, position: number): JSDocTag | undefin /** @internal */ export function getPropertiesForObjectExpression(contextualType: Type, completionsType: Type | undefined, obj: ObjectLiteralExpression | JsxAttributes, checker: TypeChecker): Symbol[] { const hasCompletionsType = completionsType && completionsType !== contextualType; - const promiseFilteredContextualType = checker.getUnionType(filter(contextualType.flags & TypeFlags.Union ? (contextualType as UnionType).types : [contextualType], t => !checker.getAwaitedType(t) || !checker.getPromisedTypeOfPromise(t))); + const promiseFilteredContextualType = checker.getUnionType( + filter( + contextualType.flags & TypeFlags.Union ? + (contextualType as UnionType).types : + [contextualType], t => !checker.getPromisedTypeOfPromise(t) + ) + ); const type = hasCompletionsType && !(completionsType.flags & TypeFlags.AnyOrUnknown) ? checker.getUnionType([promiseFilteredContextualType, completionsType]) : promiseFilteredContextualType; From 3d1aa62436e14963d16d443d9ef8e4d7d1e4a0b5 Mon Sep 17 00:00:00 2001 From: syi0808 Date: Fri, 9 Aug 2024 09:04:01 +0900 Subject: [PATCH 7/7] formatting --- src/services/completions.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/services/completions.ts b/src/services/completions.ts index 6ffd73dcb3367..cf3f1cdf39efa 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -5474,10 +5474,11 @@ export function getPropertiesForObjectExpression(contextualType: Type, completio const hasCompletionsType = completionsType && completionsType !== contextualType; const promiseFilteredContextualType = checker.getUnionType( filter( - contextualType.flags & TypeFlags.Union ? + contextualType.flags & TypeFlags.Union ? (contextualType as UnionType).types : - [contextualType], t => !checker.getPromisedTypeOfPromise(t) - ) + [contextualType], + t => !checker.getPromisedTypeOfPromise(t), + ), ); const type = hasCompletionsType && !(completionsType.flags & TypeFlags.AnyOrUnknown) ? checker.getUnionType([promiseFilteredContextualType, completionsType])