Skip to content

Commit a95a25b

Browse files
authored
Fix completions of optional properties in generic positions (#33937)
* fixes #30507 * Add test case for generic Partial type * Fixes #28470 * Simplify contextFlags binary check * Add string literal completion test * Fix ContextFlags typings * Speed up inference expression for completion * Fix baseline merge * Make contextFlags internal * Reapply readonly array changes * accept baselines * Fix generic completion tests * Re-merge ContextFlags * Don’t change type during inference * Fix typos and superfluous undefined arguments * Add test for completions in unconstrained generic object literal
1 parent 45d0ef9 commit a95a25b

12 files changed

+202
-14
lines changed

src/compiler/checker.ts

+10-12
Original file line numberDiff line numberDiff line change
@@ -174,12 +174,6 @@ namespace ts {
174174
IsForSignatureHelp = 1 << 4, // Call resolution for purposes of signature help
175175
}
176176

177-
const enum ContextFlags {
178-
None = 0,
179-
Signature = 1 << 0, // Obtaining contextual signature
180-
NoConstraints = 1 << 1, // Don't obtain type variable constraints
181-
}
182-
183177
const enum AccessFlags {
184178
None = 0,
185179
NoIndexSignatures = 1 << 0,
@@ -454,9 +448,9 @@ namespace ts {
454448
},
455449
getAugmentedPropertiesOfType,
456450
getRootSymbols,
457-
getContextualType: nodeIn => {
451+
getContextualType: (nodeIn: Expression, contextFlags?: ContextFlags) => {
458452
const node = getParseTreeNode(nodeIn, isExpression);
459-
return node ? getContextualType(node) : undefined;
453+
return node ? getContextualType(node, contextFlags) : undefined;
460454
},
461455
getContextualTypeForObjectLiteralElement: nodeIn => {
462456
const node = getParseTreeNode(nodeIn, isObjectLiteralElementLike);
@@ -20948,19 +20942,23 @@ namespace ts {
2094820942
}
2094920943

2095020944
// In a typed function call, an argument or substitution expression is contextually typed by the type of the corresponding parameter.
20951-
function getContextualTypeForArgument(callTarget: CallLikeExpression, arg: Expression): Type | undefined {
20945+
function getContextualTypeForArgument(callTarget: CallLikeExpression, arg: Expression, contextFlags?: ContextFlags): Type | undefined {
2095220946
const args = getEffectiveCallArguments(callTarget);
2095320947
const argIndex = args.indexOf(arg); // -1 for e.g. the expression of a CallExpression, or the tag of a TaggedTemplateExpression
20954-
return argIndex === -1 ? undefined : getContextualTypeForArgumentAtIndex(callTarget, argIndex);
20948+
return argIndex === -1 ? undefined : getContextualTypeForArgumentAtIndex(callTarget, argIndex, contextFlags);
2095520949
}
2095620950

20957-
function getContextualTypeForArgumentAtIndex(callTarget: CallLikeExpression, argIndex: number): Type {
20951+
function getContextualTypeForArgumentAtIndex(callTarget: CallLikeExpression, argIndex: number, contextFlags?: ContextFlags): Type {
2095820952
// If we're already in the process of resolving the given signature, don't resolve again as
2095920953
// that could cause infinite recursion. Instead, return anySignature.
2096020954
const signature = getNodeLinks(callTarget).resolvedSignature === resolvingSignature ? resolvingSignature : getResolvedSignature(callTarget);
2096120955
if (isJsxOpeningLikeElement(callTarget) && argIndex === 0) {
2096220956
return getEffectiveFirstArgumentForJsxSignature(signature, callTarget);
2096320957
}
20958+
if (contextFlags && contextFlags & ContextFlags.Completion && signature.target) {
20959+
const baseSignature = getBaseSignature(signature.target);
20960+
return intersectTypes(getTypeAtPosition(signature, argIndex), getTypeAtPosition(baseSignature, argIndex));
20961+
}
2096420962
return getTypeAtPosition(signature, argIndex);
2096520963
}
2096620964

@@ -21352,7 +21350,7 @@ namespace ts {
2135221350
}
2135321351
/* falls through */
2135421352
case SyntaxKind.NewExpression:
21355-
return getContextualTypeForArgument(<CallExpression | NewExpression>parent, node);
21353+
return getContextualTypeForArgument(<CallExpression | NewExpression>parent, node, contextFlags);
2135621354
case SyntaxKind.TypeAssertionExpression:
2135721355
case SyntaxKind.AsExpression:
2135821356
return isConstTypeReference((<AssertionExpression>parent).type) ? undefined : getTypeFromTypeNode((<AssertionExpression>parent).type);

src/compiler/types.ts

+10
Original file line numberDiff line numberDiff line change
@@ -3371,8 +3371,10 @@ namespace ts {
33713371

33723372
getFullyQualifiedName(symbol: Symbol): string;
33733373
getAugmentedPropertiesOfType(type: Type): Symbol[];
3374+
33743375
getRootSymbols(symbol: Symbol): readonly Symbol[];
33753376
getContextualType(node: Expression): Type | undefined;
3377+
/* @internal */ getContextualType(node: Expression, contextFlags?: ContextFlags): Type | undefined; // eslint-disable-line @typescript-eslint/unified-signatures
33763378
/* @internal */ getContextualTypeForObjectLiteralElement(element: ObjectLiteralElementLike): Type | undefined;
33773379
/* @internal */ getContextualTypeForArgumentAtIndex(call: CallLikeExpression, argIndex: number): Type | undefined;
33783380
/* @internal */ getContextualTypeForJsxAttribute(attribute: JsxAttribute | JsxSpreadAttribute): Type | undefined;
@@ -3532,6 +3534,14 @@ namespace ts {
35323534
Subtype
35333535
}
35343536

3537+
/* @internal */
3538+
export const enum ContextFlags {
3539+
None = 0,
3540+
Signature = 1 << 0, // Obtaining contextual signature
3541+
NoConstraints = 1 << 1, // Don't obtain type variable constraints
3542+
Completion = 1 << 2, // Obtaining constraint type for completion
3543+
}
3544+
35353545
// NOTE: If modifying this enum, must modify `TypeFormatFlags` too!
35363546
export const enum NodeBuilderFlags {
35373547
None = 0,

src/harness/fourslash.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -775,7 +775,9 @@ namespace FourSlash {
775775
private verifyCompletionsWorker(options: FourSlashInterface.VerifyCompletionsOptions): void {
776776
const actualCompletions = this.getCompletionListAtCaret({ ...options.preferences, triggerCharacter: options.triggerCharacter })!;
777777
if (!actualCompletions) {
778-
if (ts.hasProperty(options, "exact") && options.exact === undefined) return;
778+
if (ts.hasProperty(options, "exact") && (options.exact === undefined || ts.isArray(options.exact) && !options.exact.length)) {
779+
return;
780+
}
779781
this.raiseError(`No completions at position '${this.currentCaretPosition}'.`);
780782
}
781783

src/services/completions.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1761,7 +1761,7 @@ namespace ts.Completions {
17611761
let existingMembers: readonly Declaration[] | undefined;
17621762

17631763
if (objectLikeContainer.kind === SyntaxKind.ObjectLiteralExpression) {
1764-
const typeForObject = typeChecker.getContextualType(objectLikeContainer);
1764+
const typeForObject = typeChecker.getContextualType(objectLikeContainer, ContextFlags.Completion);
17651765
if (!typeForObject) return GlobalsSearch.Fail;
17661766
isNewIdentifierLocation = hasIndexSignature(typeForObject);
17671767
typeMembers = getPropertiesForObjectExpression(typeForObject, objectLikeContainer, typeChecker);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/// <reference path="fourslash.ts" />
2+
// @strict: true
3+
4+
////function f<T>(x: T) {
5+
//// return x;
6+
////}
7+
////
8+
////f({ /**/ });
9+
10+
11+
verify.completions({
12+
marker: "",
13+
exact: []
14+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/// <reference path="fourslash.ts" />
2+
// @strict: true
3+
4+
//// declare function get<T, K extends keyof T>(obj: T, key: K): T[K];
5+
//// get({ hello: 123, world: 456 }, "/**/");
6+
7+
verify.completions({
8+
marker: "",
9+
includes: ['hello', 'world']
10+
});
11+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/// <reference path="fourslash.ts" />
2+
// @strict: true
3+
4+
//// interface MyOptions {
5+
//// hello?: boolean;
6+
//// world?: boolean;
7+
//// }
8+
//// declare function bar<T extends MyOptions>(options?: Partial<T>): void;
9+
//// bar({ hello, /*1*/ });
10+
11+
verify.completions({
12+
marker: '1',
13+
includes: [
14+
{
15+
sortText: completion.SortText.OptionalMember,
16+
name: 'world'
17+
},
18+
]
19+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/// <reference path="fourslash.ts" />
2+
// @strict: true
3+
4+
//// interface Options {
5+
//// someFunction?: () => string
6+
//// anotherFunction?: () => string
7+
//// }
8+
////
9+
//// export class Clazz<T extends Options> {
10+
//// constructor(public a: T) {}
11+
//// }
12+
////
13+
//// new Clazz({ /*1*/ })
14+
15+
verify.completions({
16+
marker: '1',
17+
includes: [
18+
{
19+
sortText: completion.SortText.OptionalMember,
20+
name: 'someFunction'
21+
},
22+
{
23+
sortText: completion.SortText.OptionalMember,
24+
name: 'anotherFunction'
25+
},
26+
]
27+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/// <reference path="fourslash.ts" />
2+
// @strict: true
3+
4+
//// interface DeepOptions {
5+
//// another?: boolean;
6+
//// }
7+
//// interface MyOptions {
8+
//// hello?: boolean;
9+
//// world?: boolean;
10+
//// deep?: DeepOptions
11+
//// }
12+
//// declare function bar<T extends MyOptions>(options?: Partial<T>): void;
13+
//// bar({ deep: {/*1*/} });
14+
15+
verify.completions({
16+
marker: '1',
17+
includes: [
18+
{
19+
sortText: completion.SortText.OptionalMember,
20+
name: 'another'
21+
},
22+
]
23+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/// <reference path="fourslash.ts" />
2+
// @strict: true
3+
4+
//// interface Foo {
5+
//// a_a: boolean;
6+
//// a_b: boolean;
7+
//// a_c: boolean;
8+
//// b_a: boolean;
9+
//// }
10+
//// function partialFoo<T extends Partial<Foo>>(t: T) {return t}
11+
//// partialFoo({ /*1*/ });
12+
13+
verify.completions({
14+
marker: '1',
15+
includes: [
16+
{
17+
sortText: completion.SortText.OptionalMember,
18+
name: 'a_a'
19+
},
20+
{
21+
sortText: completion.SortText.OptionalMember,
22+
name: 'a_b'
23+
},
24+
{
25+
sortText: completion.SortText.OptionalMember,
26+
name: 'a_c'
27+
},
28+
{
29+
sortText: completion.SortText.OptionalMember,
30+
name: 'b_a'
31+
},
32+
]
33+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/// <reference path="fourslash.ts" />
2+
// @strict: true
3+
4+
//// interface Foo {
5+
//// a: boolean;
6+
//// }
7+
//// function partialFoo<T extends Partial<Foo>>(x: T, y: T) {return t}
8+
//// partialFoo({ a: true, b: true }, { /*1*/ });
9+
10+
verify.completions({
11+
marker: '1',
12+
includes: [
13+
{
14+
sortText: completion.SortText.OptionalMember,
15+
name: 'a'
16+
},
17+
{
18+
sortText: completion.SortText.OptionalMember,
19+
name: 'b'
20+
}
21+
]
22+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/// <reference path="fourslash.ts" />
2+
// @strict: true
3+
4+
////interface Foo {
5+
//// a: boolean;
6+
////}
7+
////function partialFoo<T extends Partial<Foo>>(x: T, y: T extends { b?: boolean } ? T & { c: true } : T) {
8+
//// return x;
9+
////}
10+
////
11+
////partialFoo({ a: true, b: true }, { /*1*/ });
12+
13+
14+
verify.completions({
15+
marker: '1',
16+
includes: [
17+
{
18+
sortText: completion.SortText.OptionalMember,
19+
name: 'a'
20+
},
21+
{
22+
sortText: completion.SortText.OptionalMember,
23+
name: 'b'
24+
},
25+
{
26+
name: 'c'
27+
}
28+
]
29+
})

0 commit comments

Comments
 (0)