diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3f550efce4051..0b771ed2f0ba8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -21160,9 +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 && !hasTypeArguments(callTarget)) { const baseSignature = getBaseSignature(signature.target); - return intersectTypes(getTypeAtPosition(signature, argIndex), getTypeAtPosition(baseSignature, argIndex)); + 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..a71f9f26a68d2 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2433,6 +2433,10 @@ namespace ts { return (node as ParameterDeclaration).dotDotDotToken !== undefined || !!type && type.kind === SyntaxKind.JSDocVariadicType; } + export function hasTypeArguments(node: Node): node is HasTypeArguments { + return !!(node as HasTypeArguments).typeArguments; + } + 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/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 { 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' + }] +}); 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' }, + ] +});