diff --git a/src/services/completions.ts b/src/services/completions.ts
index 65de51651a0f1..3291902986fa0 100644
--- a/src/services/completions.ts
+++ b/src/services/completions.ts
@@ -2666,7 +2666,7 @@ export function getCompletionEntriesFromSymbols(
includeSymbol = false,
): UniqueNameSet {
const start = timestamp();
- const variableOrParameterDeclaration = getVariableOrParameterDeclaration(contextToken, location);
+ const closestSymbolDeclaration = getClosestSymbolDeclaration(contextToken, location);
const useSemicolons = probablyUsesSemicolons(sourceFile);
const typeChecker = program.getTypeChecker();
// Tracks unique names.
@@ -2745,26 +2745,33 @@ export function getCompletionEntriesFromSymbols(
}
// Filter out variables from their own initializers
// `const a = /* no 'a' here */`
- if (tryCast(variableOrParameterDeclaration, isVariableDeclaration) && symbol.valueDeclaration === variableOrParameterDeclaration) {
+ if (tryCast(closestSymbolDeclaration, isVariableDeclaration) && symbol.valueDeclaration === closestSymbolDeclaration) {
return false;
}
- // Filter out parameters from their own initializers
+ // Filter out current and latter parameters from defaults
// `function f(a = /* no 'a' and 'b' here */, b) { }` or
- // `function f(a: T) { }`
+ // `function f(a: T, b: T2) { }`
const symbolDeclaration = symbol.valueDeclaration ?? symbol.declarations?.[0];
- if (
- variableOrParameterDeclaration && symbolDeclaration && (
- (isTypeParameterDeclaration(variableOrParameterDeclaration) && isTypeParameterDeclaration(symbolDeclaration)) ||
- (isParameter(variableOrParameterDeclaration) && isParameter(symbolDeclaration))
- )
- ) {
- const symbolDeclarationPos = symbolDeclaration.pos;
- const parameters = isParameter(variableOrParameterDeclaration) ? variableOrParameterDeclaration.parent.parameters :
- isInferTypeNode(variableOrParameterDeclaration.parent) ? undefined :
- variableOrParameterDeclaration.parent.typeParameters;
- if (symbolDeclarationPos >= variableOrParameterDeclaration.pos && parameters && symbolDeclarationPos < parameters.end) {
- return false;
+ if (closestSymbolDeclaration && symbolDeclaration) {
+ if (isParameter(closestSymbolDeclaration) && isParameter(symbolDeclaration)) {
+ const parameters = closestSymbolDeclaration.parent.parameters;
+ if (symbolDeclaration.pos >= closestSymbolDeclaration.pos && symbolDeclaration.pos < parameters.end) {
+ return false;
+ }
+ }
+ else if (isTypeParameterDeclaration(closestSymbolDeclaration) && isTypeParameterDeclaration(symbolDeclaration)) {
+ if (closestSymbolDeclaration === symbolDeclaration && contextToken?.kind === SyntaxKind.ExtendsKeyword) {
+ // filter out the directly self-recursive type parameters
+ // `type A = K`
+ return false;
+ }
+ if (isInTypeParameterDefault(contextToken) && !isInferTypeNode(closestSymbolDeclaration.parent)) {
+ const typeParameters = closestSymbolDeclaration.parent.typeParameters;
+ if (typeParameters && symbolDeclaration.pos >= closestSymbolDeclaration.pos && symbolDeclaration.pos < typeParameters.end) {
+ return false;
+ }
+ }
}
}
@@ -6001,20 +6008,39 @@ function isModuleSpecifierMissingOrEmpty(specifier: ModuleReference | Expression
return !tryCast(isExternalModuleReference(specifier) ? specifier.expression : specifier, isStringLiteralLike)?.text;
}
-function getVariableOrParameterDeclaration(contextToken: Node | undefined, location: Node) {
+function getClosestSymbolDeclaration(contextToken: Node | undefined, location: Node) {
if (!contextToken) return;
- const possiblyParameterDeclaration = findAncestor(contextToken, node =>
+ let closestDeclaration = findAncestor(contextToken, node =>
isFunctionBlock(node) || isArrowFunctionBody(node) || isBindingPattern(node)
? "quit"
: ((isParameter(node) || isTypeParameterDeclaration(node)) && !isIndexSignatureDeclaration(node.parent)));
- const possiblyVariableDeclaration = findAncestor(location, node =>
- isFunctionBlock(node) || isArrowFunctionBody(node) || isBindingPattern(node)
- ? "quit"
- : isVariableDeclaration(node));
+ if (!closestDeclaration) {
+ closestDeclaration = findAncestor(location, node =>
+ isFunctionBlock(node) || isArrowFunctionBody(node) || isBindingPattern(node)
+ ? "quit"
+ : isVariableDeclaration(node));
+ }
+ return closestDeclaration as ParameterDeclaration | TypeParameterDeclaration | VariableDeclaration | undefined;
+}
+
+function isInTypeParameterDefault(contextToken: Node | undefined) {
+ if (!contextToken) {
+ return false;
+ }
- return (possiblyParameterDeclaration || possiblyVariableDeclaration) as ParameterDeclaration | TypeParameterDeclaration | VariableDeclaration | undefined;
+ let node = contextToken;
+ let parent = contextToken.parent;
+ while (parent) {
+ if (isTypeParameterDeclaration(parent)) {
+ return parent.default === node || node.kind === SyntaxKind.EqualsToken;
+ }
+ node = parent;
+ parent = parent.parent;
+ }
+
+ return false;
}
function isArrowFunctionBody(node: Node) {
diff --git a/tests/cases/fourslash/completionsForLatterTypeParametersInConstraints1.ts b/tests/cases/fourslash/completionsForLatterTypeParametersInConstraints1.ts
new file mode 100644
index 0000000000000..1ef26bc74dc24
--- /dev/null
+++ b/tests/cases/fourslash/completionsForLatterTypeParametersInConstraints1.ts
@@ -0,0 +1,17 @@
+///
+
+//// // https://github.com/microsoft/TypeScript/issues/56474
+//// function test(a: First, b: Second) {}
+//// type A1 = K
+
+verify.completions({
+ marker: ["1"],
+ includes: ["Second"],
+ excludes: ["First"],
+});
+
+verify.completions({
+ marker: ["2"],
+ includes: ["L"],
+ excludes: ["K"],
+});
diff --git a/tests/cases/fourslash/completionsForSelfTypeParameterInConstraint1.ts b/tests/cases/fourslash/completionsForSelfTypeParameterInConstraint1.ts
new file mode 100644
index 0000000000000..89eb4631afc5a
--- /dev/null
+++ b/tests/cases/fourslash/completionsForSelfTypeParameterInConstraint1.ts
@@ -0,0 +1,15 @@
+///
+
+//// type StateMachine = {
+//// initial?: "states" extends keyof Config ? keyof Config["states"] : never;
+//// states?: Record;
+//// };
+
+//// declare function createMachine>(
+//// config: Config,
+//// ): void;
+
+verify.completions({
+ marker: ["1"],
+ includes: ["Config"],
+});
diff --git a/tests/cases/fourslash/noCompletionsForCurrentOrLaterParameters.ts b/tests/cases/fourslash/noCompletionsForCurrentOrLaterParametersInDefaults.ts
similarity index 92%
rename from tests/cases/fourslash/noCompletionsForCurrentOrLaterParameters.ts
rename to tests/cases/fourslash/noCompletionsForCurrentOrLaterParametersInDefaults.ts
index 6ed833c16f428..46035a64fb388 100644
--- a/tests/cases/fourslash/noCompletionsForCurrentOrLaterParameters.ts
+++ b/tests/cases/fourslash/noCompletionsForCurrentOrLaterParametersInDefaults.ts
@@ -9,7 +9,6 @@
//// const f5 = (a, b = (c = /*7*/, e) => { }, d = b) => { }
////
//// type A1 = K
-//// type A2 = K
verify.completions({
marker: ["1", "2"],
@@ -42,6 +41,6 @@ verify.completions({
})
verify.completions({
- marker: ["T1", "T2"],
+ marker: ["T1"],
excludes: ["K", "L"],
})