diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts
index c5701087ebfd4..790f97db3eb8e 100644
--- a/src/compiler/checker.ts
+++ b/src/compiler/checker.ts
@@ -42606,6 +42606,48 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return undefined;
}
+ function getSignaturesFromCallLike(node: CallLikeExpression): readonly Signature[] {
+ switch (node.kind) {
+ case SyntaxKind.CallExpression:
+ case SyntaxKind.Decorator:
+ return getSignaturesOfType(
+ getTypeOfExpression(node.expression),
+ SignatureKind.Call,
+ );
+ case SyntaxKind.NewExpression:
+ return getSignaturesOfType(
+ getTypeOfExpression(node.expression),
+ SignatureKind.Construct,
+ );
+ case SyntaxKind.JsxSelfClosingElement:
+ case SyntaxKind.JsxOpeningElement:
+ if (isJsxIntrinsicTagName(node.tagName)) return [];
+ return getSignaturesOfType(
+ getTypeOfExpression(node.tagName),
+ SignatureKind.Call,
+ );
+ case SyntaxKind.TaggedTemplateExpression:
+ return getSignaturesOfType(
+ getTypeOfExpression(node.tag),
+ SignatureKind.Call,
+ );
+ case SyntaxKind.BinaryExpression:
+ case SyntaxKind.JsxOpeningFragment:
+ return [];
+ }
+ }
+
+ function getTypeParameterConstraintForPositionAcrossSignatures(signatures: readonly Signature[], position: number) {
+ const relevantTypeParameterConstraints = flatMap(signatures, signature => {
+ const relevantTypeParameter = signature.typeParameters?.[position];
+ if (relevantTypeParameter === undefined) return [];
+ const relevantConstraint = getConstraintOfTypeParameter(relevantTypeParameter);
+ if (relevantConstraint === undefined) return [];
+ return [relevantConstraint];
+ });
+ return getUnionType(relevantTypeParameterConstraints);
+ }
+
function checkTypeReferenceNode(node: TypeReferenceNode | ExpressionWithTypeArguments) {
checkGrammarTypeArguments(node, node.typeArguments);
if (node.kind === SyntaxKind.TypeReference && !isInJSFile(node) && !isInJSDoc(node) && node.typeArguments && node.typeName.end !== node.typeArguments.pos) {
@@ -42644,12 +42686,62 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
function getTypeArgumentConstraint(node: TypeNode): Type | undefined {
- const typeReferenceNode = tryCast(node.parent, isTypeReferenceType);
- if (!typeReferenceNode) return undefined;
- const typeParameters = getTypeParametersForTypeReferenceOrImport(typeReferenceNode);
- if (!typeParameters) return undefined;
- const constraint = getConstraintOfTypeParameter(typeParameters[typeReferenceNode.typeArguments!.indexOf(node)]);
- return constraint && instantiateType(constraint, createTypeMapper(typeParameters, getEffectiveTypeArguments(typeReferenceNode, typeParameters)));
+ let typeArgumentPosition;
+ if (
+ "typeArguments" in node.parent && // eslint-disable-line local/no-in-operator
+ Array.isArray(node.parent.typeArguments)
+ ) {
+ typeArgumentPosition = node.parent.typeArguments.indexOf(node);
+ }
+
+ if (typeArgumentPosition !== undefined) {
+ // The node could be a type argument of a call, a `new` expression, a decorator, an
+ // instantiation expression, or a generic type instantiation.
+
+ if (isCallLikeExpression(node.parent)) {
+ return getTypeParameterConstraintForPositionAcrossSignatures(
+ getSignaturesFromCallLike(node.parent),
+ typeArgumentPosition,
+ );
+ }
+
+ if (isDecorator(node.parent.parent)) {
+ return getTypeParameterConstraintForPositionAcrossSignatures(
+ getSignaturesFromCallLike(node.parent.parent),
+ typeArgumentPosition,
+ );
+ }
+
+ if (isExpressionWithTypeArguments(node.parent) && isExpressionStatement(node.parent.parent)) {
+ const uninstantiatedType = checkExpression(node.parent.expression);
+
+ const callConstraint = getTypeParameterConstraintForPositionAcrossSignatures(
+ getSignaturesOfType(uninstantiatedType, SignatureKind.Call),
+ typeArgumentPosition,
+ );
+ const constructConstraint = getTypeParameterConstraintForPositionAcrossSignatures(
+ getSignaturesOfType(uninstantiatedType, SignatureKind.Construct),
+ typeArgumentPosition,
+ );
+
+ // An instantiation expression instantiates both call and construct signatures, so
+ // if both exist type arguments must be assignable to both constraints.
+ if (constructConstraint.flags & TypeFlags.Never) return callConstraint;
+ if (callConstraint.flags & TypeFlags.Never) return constructConstraint;
+ return getIntersectionType([callConstraint, constructConstraint]);
+ }
+
+ if (isTypeReferenceType(node.parent)) {
+ const typeParameters = getTypeParametersForTypeReferenceOrImport(node.parent);
+ if (!typeParameters) return undefined;
+ const relevantTypeParameter = typeParameters[typeArgumentPosition];
+ const constraint = getConstraintOfTypeParameter(relevantTypeParameter);
+ return constraint && instantiateType(
+ constraint,
+ createTypeMapper(typeParameters, getEffectiveTypeArguments(node.parent, typeParameters)),
+ );
+ }
+ }
}
function checkTypeQuery(node: TypeQueryNode) {
diff --git a/src/services/completions.ts b/src/services/completions.ts
index dc01ea8ede4b9..9e90922d445d4 100644
--- a/src/services/completions.ts
+++ b/src/services/completions.ts
@@ -256,7 +256,6 @@ import {
isTypeOnlyImportDeclaration,
isTypeOnlyImportOrExportDeclaration,
isTypeParameterDeclaration,
- isTypeReferenceType,
isValidTypeOnlyAliasUseSite,
isVariableDeclaration,
isVariableLike,
@@ -5769,8 +5768,9 @@ function tryGetTypeLiteralNode(node: Node): TypeLiteralNode | undefined {
function getConstraintOfTypeArgumentProperty(node: Node, checker: TypeChecker): Type | undefined {
if (!node) return undefined;
- if (isTypeNode(node) && isTypeReferenceType(node.parent)) {
- return checker.getTypeArgumentConstraint(node);
+ if (isTypeNode(node)) {
+ const constraint = checker.getTypeArgumentConstraint(node);
+ if (constraint) return constraint;
}
const t = getConstraintOfTypeArgumentProperty(node.parent, checker);
diff --git a/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter10.ts b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter10.ts
new file mode 100644
index 0000000000000..892e88a882558
--- /dev/null
+++ b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter10.ts
@@ -0,0 +1,38 @@
+///
+
+////interface Foo {
+//// one: string;
+//// two: number;
+////}
+////interface Bar {
+//// three: boolean;
+//// four: {
+//// five: unknown;
+//// };
+////}
+////
+////function a() {}
+////a<{/*0*/}>();
+////
+////var b = () => () => {};
+////b()<{/*1*/}>();
+////
+////declare function c(): void
+////declare function c(): void
+////c<{/*2*/}>();
+////
+////function d() {}
+////d<{/*3*/}, {/*4*/}>();
+////d();
+////
+////(() => {})<{/*6*/}>();
+
+verify.completions(
+ { marker: "0", unsorted: ["one", "two"], isNewIdentifierLocation: true },
+ { marker: "1", unsorted: ["one", "two"], isNewIdentifierLocation: true },
+ { marker: "2", unsorted: ["one", "two", "three", "four"], isNewIdentifierLocation: true },
+ { marker: "3", unsorted: ["one", "two"], isNewIdentifierLocation: true },
+ { marker: "4", unsorted: ["three", "four"], isNewIdentifierLocation: true },
+ { marker: "5", unsorted: ["five"], isNewIdentifierLocation: true },
+ { marker: "6", unsorted: ["one", "two"], isNewIdentifierLocation: true },
+);
diff --git a/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter11.ts b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter11.ts
new file mode 100644
index 0000000000000..634746dd38582
--- /dev/null
+++ b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter11.ts
@@ -0,0 +1,32 @@
+///
+
+////interface Foo {
+//// one: string;
+//// two: number;
+////}
+////interface Bar {
+//// three: boolean;
+//// four: symbol;
+////}
+////
+////class A {}
+////new A<{/*0*/}>();
+////
+////class B {}
+////new B<{/*1*/}, {/*2*/}>();
+////
+////declare const C: {
+//// new (): unknown
+//// new (): unknown
+////}
+////new C<{/*3*/}>()
+////
+////new (class {})<{/*4*/}>();
+
+verify.completions(
+ { marker: "0", unsorted: ["one", "two"], isNewIdentifierLocation: true },
+ { marker: "1", unsorted: ["one", "two"], isNewIdentifierLocation: true },
+ { marker: "2", unsorted: ["three", "four"], isNewIdentifierLocation: true },
+ { marker: "3", unsorted: ["one", "two", "three", "four"], isNewIdentifierLocation: true },
+ { marker: "4", unsorted: ["one", "two"], isNewIdentifierLocation: true },
+);
diff --git a/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter12.ts b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter12.ts
new file mode 100644
index 0000000000000..9bf8ec21afaae
--- /dev/null
+++ b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter12.ts
@@ -0,0 +1,25 @@
+///
+
+////interface Foo {
+//// kind: 'foo';
+//// one: string;
+////}
+////interface Bar {
+//// kind: 'bar';
+//// two: number;
+////}
+////
+////declare function a(): void
+////declare function a(): void
+////a<{ kind: 'bar', /*0*/ }>();
+////
+////declare function b(kind: 'foo'): void
+////declare function b(kind: 'bar'): void
+////b<{/*1*/}>('bar');
+
+// The completion lists are unfortunately not narrowed here (ideally only
+// properties of `Bar` would be suggested).
+verify.completions(
+ { marker: "0", unsorted: ["one", "two"], isNewIdentifierLocation: true },
+ { marker: "1", unsorted: ["kind", "one", "two"], isNewIdentifierLocation: true },
+);
diff --git a/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter13.ts b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter13.ts
new file mode 100644
index 0000000000000..5797134b2b15e
--- /dev/null
+++ b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter13.ts
@@ -0,0 +1,18 @@
+///
+
+// @jsx: preserve
+// @filename: a.tsx
+////interface Foo {
+//// one: string;
+//// two: number;
+////}
+////
+////const Component = () => <>>;
+////
+////>;
+/////>;
+
+verify.completions(
+ { marker: "0", unsorted: ["one", "two"], isNewIdentifierLocation: true },
+ { marker: "1", unsorted: ["one", "two"], isNewIdentifierLocation: true },
+);
diff --git a/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter14.ts b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter14.ts
new file mode 100644
index 0000000000000..79aa45875715e
--- /dev/null
+++ b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter14.ts
@@ -0,0 +1,10 @@
+///
+
+////interface Foo {
+//// one: string;
+//// two: number;
+////}
+////declare function f(x: TemplateStringsArray): void;
+////f<{/*0*/}>``;
+
+verify.completions({ marker: "0", unsorted: ["one", "two"], isNewIdentifierLocation: true });
diff --git a/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter15.ts b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter15.ts
new file mode 100644
index 0000000000000..352facb90a46d
--- /dev/null
+++ b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter15.ts
@@ -0,0 +1,15 @@
+///
+
+////interface Foo {
+//// one: string;
+//// two: number;
+////}
+////
+////declare function decorator(originalMethod: unknown, _context: unknown): never
+////
+////class {
+//// @decorator<{/*0*/}>
+//// method() {}
+////}
+
+verify.completions({ marker: "0", unsorted: ["one", "two"], isNewIdentifierLocation: true });
diff --git a/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter16.ts b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter16.ts
new file mode 100644
index 0000000000000..4c32c2d19fc22
--- /dev/null
+++ b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter16.ts
@@ -0,0 +1,35 @@
+///
+
+////interface Foo {
+//// one: string;
+//// two: number;
+////}
+////interface Bar {
+//// three: boolean;
+//// four: {
+//// five: unknown;
+//// };
+////}
+////
+////(() => {})<{/*0*/}>;
+////
+////(class {})<{/*1*/}>;
+////
+////declare const a: {
+//// new (): {};
+//// (): {};
+////}
+////a<{/*2*/}>;
+////
+////declare const b: {
+//// new (): {};
+//// (): {};
+////}
+////b<{/*3*/}>;
+
+verify.completions(
+ { marker: "0", unsorted: ["one", "two"], isNewIdentifierLocation: true },
+ { marker: "1", unsorted: ["one", "two"], isNewIdentifierLocation: true },
+ { marker: "2", unsorted: ["one", "two", "three", "four"], isNewIdentifierLocation: true },
+ { marker: "3", unsorted: [], isNewIdentifierLocation: true },
+);