From 5762387bbdf40c2527c071c9b2e7a385087268c3 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Thu, 31 Oct 2019 09:46:18 -0700 Subject: [PATCH 1/5] Weird fix --- src/compiler/checker.ts | 11 +++++- .../completionsGenericIndexedAccess1.ts | 18 ++++++++++ .../completionsGenericIndexedAccess2.ts | 34 +++++++++++++++++++ 3 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 tests/cases/fourslash/completionsGenericIndexedAccess1.ts create mode 100644 tests/cases/fourslash/completionsGenericIndexedAccess2.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3f550efce4051..1ee71cbdabda8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -21162,7 +21162,16 @@ namespace ts { } if (contextFlags && contextFlags & ContextFlags.Completion && signature.target) { const baseSignature = getBaseSignature(signature.target); - return intersectTypes(getTypeAtPosition(signature, argIndex), getTypeAtPosition(baseSignature, argIndex)); + // Only consider the type from the base signature for completions + // if it actually contributes something. The type from the contextually + // instantiated signature is the _real_ type anyway, so we should never + // let the base type _remove_ completions from the list. + const baseArgumentType = filterType( + getTypeAtPosition(baseSignature, argIndex), + t => !(t.flags & (TypeFlags.AnyOrUnknown | TypeFlags.IndexedAccess))); + if (!(baseArgumentType.flags & TypeFlags.Never)) { + return intersectTypes(getTypeAtPosition(signature, argIndex), baseArgumentType); + } } return getTypeAtPosition(signature, argIndex); } diff --git a/tests/cases/fourslash/completionsGenericIndexedAccess1.ts b/tests/cases/fourslash/completionsGenericIndexedAccess1.ts new file mode 100644 index 0000000000000..8e67f34609f95 --- /dev/null +++ b/tests/cases/fourslash/completionsGenericIndexedAccess1.ts @@ -0,0 +1,18 @@ +/// + +// #34825 + +////interface Sample { +//// addBook: { name: string, year: number } +////} +//// +////export declare function testIt(method: T[keyof T]): any +////testIt({ /**/ }); + +verify.completions({ + marker: '', + exact: [ + { name: 'name' }, + { name: 'year' }, + ] +}); diff --git a/tests/cases/fourslash/completionsGenericIndexedAccess2.ts b/tests/cases/fourslash/completionsGenericIndexedAccess2.ts new file mode 100644 index 0000000000000..698dfa6cc34fb --- /dev/null +++ b/tests/cases/fourslash/completionsGenericIndexedAccess2.ts @@ -0,0 +1,34 @@ +/// + +// #34825 + +////export type GetMethodsForType = { [K in keyof T]: +//// T[K] extends () => any ? { name: K, group: G, } : T[K] extends (s: infer U) => any ? { name: K, group: G, payload: U } : never }[keyof T]; +//// +//// +////class Sample { +//// count = 0; +//// books: { name: string, year: number }[] = [] +//// increment() { +//// this.count++ +//// this.count++ +//// } +//// +//// addBook(book: Sample["books"][0]) { +//// this.books.push(book) +//// } +////} +////export declare function testIt(): (input: any, method: GetMethodsForType) => any +//// +//// +////const t = testIt() +//// +////const i = t(null, { name: "addBook", group: "Sample", payload: { /**/ } }) + +verify.completions({ + marker: '', + exact: [ + { name: 'name' }, + { name: 'year' }, + ] +}); From 7039e23b03bed83ac96862d7f9a05773c1073299 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Thu, 31 Oct 2019 13:26:17 -0700 Subject: [PATCH 2/5] Slightly better I guess --- src/compiler/checker.ts | 13 ++--------- src/compiler/types.ts | 15 ++++++++---- src/compiler/utilities.ts | 8 +++++++ src/services/completions.ts | 23 +++++++++++-------- .../fourslash/completionsConditionalMember.ts | 22 ++++++++++++++++++ 5 files changed, 57 insertions(+), 24 deletions(-) create mode 100644 tests/cases/fourslash/completionsConditionalMember.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 1ee71cbdabda8..40a8715f14020 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -21160,18 +21160,9 @@ namespace ts { if (isJsxOpeningLikeElement(callTarget) && argIndex === 0) { return getEffectiveFirstArgumentForJsxSignature(signature, callTarget); } - if (contextFlags && contextFlags & ContextFlags.Completion && signature.target) { + if (contextFlags && contextFlags & ContextFlags.BaseConstraint && signature.target && !tryCast(callTarget, hasTypeArguments)?.typeArguments) { const baseSignature = getBaseSignature(signature.target); - // Only consider the type from the base signature for completions - // if it actually contributes something. The type from the contextually - // instantiated signature is the _real_ type anyway, so we should never - // let the base type _remove_ completions from the list. - const baseArgumentType = filterType( - getTypeAtPosition(baseSignature, argIndex), - t => !(t.flags & (TypeFlags.AnyOrUnknown | TypeFlags.IndexedAccess))); - if (!(baseArgumentType.flags & TypeFlags.Never)) { - return intersectTypes(getTypeAtPosition(signature, argIndex), baseArgumentType); - } + return getTypeAtPosition(baseSignature, argIndex); } return getTypeAtPosition(signature, argIndex); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index be104d84ef041..994ddbb6dde34 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -709,6 +709,13 @@ namespace ts { | JSDocOptionalType | JSDocVariadicType; + export type HasTypeArguments = + | CallExpression + | NewExpression + | TaggedTemplateExpression + | JsxOpeningElement + | JsxSelfClosingElement; + export type HasInitializer = | HasExpressionInitializer | ForStatement @@ -3534,10 +3541,10 @@ namespace ts { /* @internal */ export const enum ContextFlags { - None = 0, - Signature = 1 << 0, // Obtaining contextual signature - NoConstraints = 1 << 1, // Don't obtain type variable constraints - Completion = 1 << 2, // Obtaining constraint type for completion + None = 0, + Signature = 1 << 0, // Obtaining contextual signature + NoConstraints = 1 << 1, // Don't obtain type variable constraints + BaseConstraint = 1 << 2, // Use base constraint type for completions } // NOTE: If modifying this enum, must modify `TypeFormatFlags` too! diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index bff2c8fc6b80a..360bc5040fb8a 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2433,6 +2433,14 @@ namespace ts { return (node as ParameterDeclaration).dotDotDotToken !== undefined || !!type && type.kind === SyntaxKind.JSDocVariadicType; } + export function hasTypeArguments(node: Node): node is HasTypeArguments { + return node.kind === SyntaxKind.CallExpression + || node.kind === SyntaxKind.NewExpression + || node.kind === SyntaxKind.TaggedTemplateExpression + || node.kind === SyntaxKind.JsxOpeningElement + || node.kind === SyntaxKind.JsxSelfClosingElement; + } + export const enum AssignmentKind { None, Definite, Compound } diff --git a/src/services/completions.ts b/src/services/completions.ts index e05baddbda34a..d7873c4448666 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -1279,7 +1279,7 @@ namespace ts.Completions { // Cursor is inside a JSX self-closing element or opening element const attrsType = jsxContainer && typeChecker.getContextualType(jsxContainer.attributes); if (!attrsType) return GlobalsSearch.Continue; - symbols = filterJsxAttributes(getPropertiesForObjectExpression(attrsType, jsxContainer!.attributes, typeChecker), jsxContainer!.attributes.properties); + symbols = filterJsxAttributes(getPropertiesForObjectExpression(attrsType, /*baseType*/ undefined, jsxContainer!.attributes, typeChecker), jsxContainer!.attributes.properties); setSortTextToOptionalMember(); completionKind = CompletionKind.MemberLike; isNewIdentifierLocation = false; @@ -1795,10 +1795,11 @@ namespace ts.Completions { let existingMembers: readonly Declaration[] | undefined; if (objectLikeContainer.kind === SyntaxKind.ObjectLiteralExpression) { - const typeForObject = typeChecker.getContextualType(objectLikeContainer, ContextFlags.Completion); - if (!typeForObject) return GlobalsSearch.Fail; - isNewIdentifierLocation = hasIndexSignature(typeForObject); - typeMembers = getPropertiesForObjectExpression(typeForObject, objectLikeContainer, typeChecker); + const instantiatedType = typeChecker.getContextualType(objectLikeContainer); + const baseType = instantiatedType && typeChecker.getContextualType(objectLikeContainer, ContextFlags.BaseConstraint); + if (!instantiatedType || !baseType) return GlobalsSearch.Fail; + isNewIdentifierLocation = hasIndexSignature(instantiatedType || baseType); + typeMembers = getPropertiesForObjectExpression(instantiatedType, baseType, objectLikeContainer, typeChecker); existingMembers = objectLikeContainer.properties; } else { @@ -2535,15 +2536,19 @@ namespace ts.Completions { return jsdoc && jsdoc.tags && (rangeContainsPosition(jsdoc, position) ? findLast(jsdoc.tags, tag => tag.pos < position) : undefined); } - function getPropertiesForObjectExpression(contextualType: Type, obj: ObjectLiteralExpression | JsxAttributes, checker: TypeChecker): Symbol[] { - return contextualType.isUnion() - ? checker.getAllPossiblePropertiesOfTypes(contextualType.types.filter(memberType => + function getPropertiesForObjectExpression(contextualType: Type, baseConstrainedType: Type | undefined, obj: ObjectLiteralExpression | JsxAttributes, checker: TypeChecker): Symbol[] { + const type = baseConstrainedType && !(baseConstrainedType.flags & TypeFlags.AnyOrUnknown) + ? checker.getUnionType([contextualType, baseConstrainedType]) + : contextualType; + + return type.isUnion() + ? checker.getAllPossiblePropertiesOfTypes(type.types.filter(memberType => // If we're providing completions for an object literal, skip primitive, array-like, or callable types since those shouldn't be implemented by object literals. !(memberType.flags & TypeFlags.Primitive || checker.isArrayLikeType(memberType) || typeHasCallOrConstructSignatures(memberType, checker) || checker.isTypeInvalidDueToUnionDiscriminant(memberType, obj)))) - : contextualType.getApparentProperties(); + : type.getApparentProperties(); } /** diff --git a/tests/cases/fourslash/completionsConditionalMember.ts b/tests/cases/fourslash/completionsConditionalMember.ts new file mode 100644 index 0000000000000..bcd1d0e9dcf73 --- /dev/null +++ b/tests/cases/fourslash/completionsConditionalMember.ts @@ -0,0 +1,22 @@ +/// + +////declare function f( +//// p: { a: T extends 'foo' ? { x: string } : { y: string } } +////): void; +//// +////f<'foo'>({ a: { /*1*/ } }); +////f({ a: { /*2*/ } }); + +verify.completions({ + marker: '1', + exact: [{ + name: 'x' + }] +}); + +verify.completions({ + marker: '2', + exact: [{ + name: 'y' + }] +}); From 70d67ab54a8b3a96e6b4bfe2fe930ac10e22e192 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Thu, 31 Oct 2019 13:50:48 -0700 Subject: [PATCH 3/5] Update APIs --- tests/baselines/reference/api/tsserverlibrary.d.ts | 1 + tests/baselines/reference/api/typescript.d.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 013739a4b3f51..48f9dc6870baf 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -502,6 +502,7 @@ declare namespace ts { } export type HasJSDoc = ParameterDeclaration | CallSignatureDeclaration | ConstructSignatureDeclaration | MethodSignature | PropertySignature | ArrowFunction | ParenthesizedExpression | SpreadAssignment | ShorthandPropertyAssignment | PropertyAssignment | FunctionExpression | LabeledStatement | ExpressionStatement | VariableStatement | FunctionDeclaration | ConstructorDeclaration | MethodDeclaration | PropertyDeclaration | AccessorDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | EnumMember | EnumDeclaration | ModuleDeclaration | ImportEqualsDeclaration | IndexSignatureDeclaration | FunctionTypeNode | ConstructorTypeNode | JSDocFunctionType | ExportDeclaration | EndOfFileToken; export type HasType = SignatureDeclaration | VariableDeclaration | ParameterDeclaration | PropertySignature | PropertyDeclaration | TypePredicateNode | ParenthesizedTypeNode | TypeOperatorNode | MappedTypeNode | AssertionExpression | TypeAliasDeclaration | JSDocTypeExpression | JSDocNonNullableType | JSDocNullableType | JSDocOptionalType | JSDocVariadicType; + export type HasTypeArguments = CallExpression | NewExpression | TaggedTemplateExpression | JsxOpeningElement | JsxSelfClosingElement; export type HasInitializer = HasExpressionInitializer | ForStatement | ForInStatement | ForOfStatement | JsxAttribute; export type HasExpressionInitializer = VariableDeclaration | ParameterDeclaration | BindingElement | PropertySignature | PropertyDeclaration | PropertyAssignment | EnumMember; export interface NodeArray extends ReadonlyArray, TextRange { diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 6078232574ce1..580624ca3935b 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -502,6 +502,7 @@ declare namespace ts { } export type HasJSDoc = ParameterDeclaration | CallSignatureDeclaration | ConstructSignatureDeclaration | MethodSignature | PropertySignature | ArrowFunction | ParenthesizedExpression | SpreadAssignment | ShorthandPropertyAssignment | PropertyAssignment | FunctionExpression | LabeledStatement | ExpressionStatement | VariableStatement | FunctionDeclaration | ConstructorDeclaration | MethodDeclaration | PropertyDeclaration | AccessorDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | EnumMember | EnumDeclaration | ModuleDeclaration | ImportEqualsDeclaration | IndexSignatureDeclaration | FunctionTypeNode | ConstructorTypeNode | JSDocFunctionType | ExportDeclaration | EndOfFileToken; export type HasType = SignatureDeclaration | VariableDeclaration | ParameterDeclaration | PropertySignature | PropertyDeclaration | TypePredicateNode | ParenthesizedTypeNode | TypeOperatorNode | MappedTypeNode | AssertionExpression | TypeAliasDeclaration | JSDocTypeExpression | JSDocNonNullableType | JSDocNullableType | JSDocOptionalType | JSDocVariadicType; + export type HasTypeArguments = CallExpression | NewExpression | TaggedTemplateExpression | JsxOpeningElement | JsxSelfClosingElement; export type HasInitializer = HasExpressionInitializer | ForStatement | ForInStatement | ForOfStatement | JsxAttribute; export type HasExpressionInitializer = VariableDeclaration | ParameterDeclaration | BindingElement | PropertySignature | PropertyDeclaration | PropertyAssignment | EnumMember; export interface NodeArray extends ReadonlyArray, TextRange { From 364a4e0325faa607d5a867ecf53571d87f3cc6a7 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Wed, 6 Nov 2019 13:01:57 -0800 Subject: [PATCH 4/5] Make `hasTypeArguments` do what it says on the tin --- 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 40a8715f14020..0b771ed2f0ba8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -21160,7 +21160,7 @@ namespace ts { if (isJsxOpeningLikeElement(callTarget) && argIndex === 0) { return getEffectiveFirstArgumentForJsxSignature(signature, callTarget); } - if (contextFlags && contextFlags & ContextFlags.BaseConstraint && signature.target && !tryCast(callTarget, hasTypeArguments)?.typeArguments) { + if (contextFlags && contextFlags & ContextFlags.BaseConstraint && signature.target && !hasTypeArguments(callTarget)) { const baseSignature = getBaseSignature(signature.target); return getTypeAtPosition(baseSignature, argIndex); } From b29d7fd554f9bb736513779715f32b81e0c5f02b Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Mon, 2 Dec 2019 10:16:28 -0800 Subject: [PATCH 5/5] Fix merge mistake --- src/compiler/utilities.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 360bc5040fb8a..a71f9f26a68d2 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2434,11 +2434,7 @@ namespace ts { } export function hasTypeArguments(node: Node): node is HasTypeArguments { - return node.kind === SyntaxKind.CallExpression - || node.kind === SyntaxKind.NewExpression - || node.kind === SyntaxKind.TaggedTemplateExpression - || node.kind === SyntaxKind.JsxOpeningElement - || node.kind === SyntaxKind.JsxSelfClosingElement; + return !!(node as HasTypeArguments).typeArguments; } export const enum AssignmentKind {