Skip to content

Commit 53039d3

Browse files
authored
Include all type parameters in completions within type parameters' constraints (#56543)
1 parent 31afb98 commit 53039d3

File tree

4 files changed

+82
-25
lines changed

4 files changed

+82
-25
lines changed

src/services/completions.ts

Lines changed: 49 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2666,7 +2666,7 @@ export function getCompletionEntriesFromSymbols(
26662666
includeSymbol = false,
26672667
): UniqueNameSet {
26682668
const start = timestamp();
2669-
const variableOrParameterDeclaration = getVariableOrParameterDeclaration(contextToken, location);
2669+
const closestSymbolDeclaration = getClosestSymbolDeclaration(contextToken, location);
26702670
const useSemicolons = probablyUsesSemicolons(sourceFile);
26712671
const typeChecker = program.getTypeChecker();
26722672
// Tracks unique names.
@@ -2745,26 +2745,33 @@ export function getCompletionEntriesFromSymbols(
27452745
}
27462746
// Filter out variables from their own initializers
27472747
// `const a = /* no 'a' here */`
2748-
if (tryCast(variableOrParameterDeclaration, isVariableDeclaration) && symbol.valueDeclaration === variableOrParameterDeclaration) {
2748+
if (tryCast(closestSymbolDeclaration, isVariableDeclaration) && symbol.valueDeclaration === closestSymbolDeclaration) {
27492749
return false;
27502750
}
27512751

2752-
// Filter out parameters from their own initializers
2752+
// Filter out current and latter parameters from defaults
27532753
// `function f(a = /* no 'a' and 'b' here */, b) { }` or
2754-
// `function f<T = /* no 'T' here */>(a: T) { }`
2754+
// `function f<T = /* no 'T' and 'T2' here */>(a: T, b: T2) { }`
27552755
const symbolDeclaration = symbol.valueDeclaration ?? symbol.declarations?.[0];
2756-
if (
2757-
variableOrParameterDeclaration && symbolDeclaration && (
2758-
(isTypeParameterDeclaration(variableOrParameterDeclaration) && isTypeParameterDeclaration(symbolDeclaration)) ||
2759-
(isParameter(variableOrParameterDeclaration) && isParameter(symbolDeclaration))
2760-
)
2761-
) {
2762-
const symbolDeclarationPos = symbolDeclaration.pos;
2763-
const parameters = isParameter(variableOrParameterDeclaration) ? variableOrParameterDeclaration.parent.parameters :
2764-
isInferTypeNode(variableOrParameterDeclaration.parent) ? undefined :
2765-
variableOrParameterDeclaration.parent.typeParameters;
2766-
if (symbolDeclarationPos >= variableOrParameterDeclaration.pos && parameters && symbolDeclarationPos < parameters.end) {
2767-
return false;
2756+
if (closestSymbolDeclaration && symbolDeclaration) {
2757+
if (isParameter(closestSymbolDeclaration) && isParameter(symbolDeclaration)) {
2758+
const parameters = closestSymbolDeclaration.parent.parameters;
2759+
if (symbolDeclaration.pos >= closestSymbolDeclaration.pos && symbolDeclaration.pos < parameters.end) {
2760+
return false;
2761+
}
2762+
}
2763+
else if (isTypeParameterDeclaration(closestSymbolDeclaration) && isTypeParameterDeclaration(symbolDeclaration)) {
2764+
if (closestSymbolDeclaration === symbolDeclaration && contextToken?.kind === SyntaxKind.ExtendsKeyword) {
2765+
// filter out the directly self-recursive type parameters
2766+
// `type A<K extends /* no 'K' here*/> = K`
2767+
return false;
2768+
}
2769+
if (isInTypeParameterDefault(contextToken) && !isInferTypeNode(closestSymbolDeclaration.parent)) {
2770+
const typeParameters = closestSymbolDeclaration.parent.typeParameters;
2771+
if (typeParameters && symbolDeclaration.pos >= closestSymbolDeclaration.pos && symbolDeclaration.pos < typeParameters.end) {
2772+
return false;
2773+
}
2774+
}
27682775
}
27692776
}
27702777

@@ -6001,20 +6008,39 @@ function isModuleSpecifierMissingOrEmpty(specifier: ModuleReference | Expression
60016008
return !tryCast(isExternalModuleReference(specifier) ? specifier.expression : specifier, isStringLiteralLike)?.text;
60026009
}
60036010

6004-
function getVariableOrParameterDeclaration(contextToken: Node | undefined, location: Node) {
6011+
function getClosestSymbolDeclaration(contextToken: Node | undefined, location: Node) {
60056012
if (!contextToken) return;
60066013

6007-
const possiblyParameterDeclaration = findAncestor(contextToken, node =>
6014+
let closestDeclaration = findAncestor(contextToken, node =>
60086015
isFunctionBlock(node) || isArrowFunctionBody(node) || isBindingPattern(node)
60096016
? "quit"
60106017
: ((isParameter(node) || isTypeParameterDeclaration(node)) && !isIndexSignatureDeclaration(node.parent)));
60116018

6012-
const possiblyVariableDeclaration = findAncestor(location, node =>
6013-
isFunctionBlock(node) || isArrowFunctionBody(node) || isBindingPattern(node)
6014-
? "quit"
6015-
: isVariableDeclaration(node));
6019+
if (!closestDeclaration) {
6020+
closestDeclaration = findAncestor(location, node =>
6021+
isFunctionBlock(node) || isArrowFunctionBody(node) || isBindingPattern(node)
6022+
? "quit"
6023+
: isVariableDeclaration(node));
6024+
}
6025+
return closestDeclaration as ParameterDeclaration | TypeParameterDeclaration | VariableDeclaration | undefined;
6026+
}
6027+
6028+
function isInTypeParameterDefault(contextToken: Node | undefined) {
6029+
if (!contextToken) {
6030+
return false;
6031+
}
60166032

6017-
return (possiblyParameterDeclaration || possiblyVariableDeclaration) as ParameterDeclaration | TypeParameterDeclaration | VariableDeclaration | undefined;
6033+
let node = contextToken;
6034+
let parent = contextToken.parent;
6035+
while (parent) {
6036+
if (isTypeParameterDeclaration(parent)) {
6037+
return parent.default === node || node.kind === SyntaxKind.EqualsToken;
6038+
}
6039+
node = parent;
6040+
parent = parent.parent;
6041+
}
6042+
6043+
return false;
60186044
}
60196045

60206046
function isArrowFunctionBody(node: Node) {
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
//// // https://github.com/microsoft/TypeScript/issues/56474
4+
//// function test<First extends S/*1*/, Second>(a: First, b: Second) {}
5+
//// type A1<K extends /*2*/, L> = K
6+
7+
verify.completions({
8+
marker: ["1"],
9+
includes: ["Second"],
10+
excludes: ["First"],
11+
});
12+
13+
verify.completions({
14+
marker: ["2"],
15+
includes: ["L"],
16+
excludes: ["K"],
17+
});
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
//// type StateMachine<Config> = {
4+
//// initial?: "states" extends keyof Config ? keyof Config["states"] : never;
5+
//// states?: Record<string, {}>;
6+
//// };
7+
8+
//// declare function createMachine<Config extends StateMachine</*1*/>>(
9+
//// config: Config,
10+
//// ): void;
11+
12+
verify.completions({
13+
marker: ["1"],
14+
includes: ["Config"],
15+
});

tests/cases/fourslash/noCompletionsForCurrentOrLaterParameters.ts renamed to tests/cases/fourslash/noCompletionsForCurrentOrLaterParametersInDefaults.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
//// const f5 = (a, b = (c = /*7*/, e) => { }, d = b) => { }
1010
////
1111
//// type A1<K = /*T1*/, L> = K
12-
//// type A2<K extends /*T2*/, L> = K
1312

1413
verify.completions({
1514
marker: ["1", "2"],
@@ -42,6 +41,6 @@ verify.completions({
4241
})
4342

4443
verify.completions({
45-
marker: ["T1", "T2"],
44+
marker: ["T1"],
4645
excludes: ["K", "L"],
4746
})

0 commit comments

Comments
 (0)