Skip to content

Commit f1f8746

Browse files
authored
Improve some completions on generic object literals (microsoft#34855)
* Weird fix * Slightly better I guess * Update APIs * Make `hasTypeArguments` do what it says on the tin * Fix merge mistake
1 parent 7fd6e0d commit f1f8746

File tree

9 files changed

+107
-15
lines changed

9 files changed

+107
-15
lines changed

src/compiler/checker.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21160,9 +21160,9 @@ namespace ts {
2116021160
if (isJsxOpeningLikeElement(callTarget) && argIndex === 0) {
2116121161
return getEffectiveFirstArgumentForJsxSignature(signature, callTarget);
2116221162
}
21163-
if (contextFlags && contextFlags & ContextFlags.Completion && signature.target) {
21163+
if (contextFlags && contextFlags & ContextFlags.BaseConstraint && signature.target && !hasTypeArguments(callTarget)) {
2116421164
const baseSignature = getBaseSignature(signature.target);
21165-
return intersectTypes(getTypeAtPosition(signature, argIndex), getTypeAtPosition(baseSignature, argIndex));
21165+
return getTypeAtPosition(baseSignature, argIndex);
2116621166
}
2116721167
return getTypeAtPosition(signature, argIndex);
2116821168
}

src/compiler/types.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -709,6 +709,13 @@ namespace ts {
709709
| JSDocOptionalType
710710
| JSDocVariadicType;
711711

712+
export type HasTypeArguments =
713+
| CallExpression
714+
| NewExpression
715+
| TaggedTemplateExpression
716+
| JsxOpeningElement
717+
| JsxSelfClosingElement;
718+
712719
export type HasInitializer =
713720
| HasExpressionInitializer
714721
| ForStatement
@@ -3537,10 +3544,10 @@ namespace ts {
35373544

35383545
/* @internal */
35393546
export const enum ContextFlags {
3540-
None = 0,
3541-
Signature = 1 << 0, // Obtaining contextual signature
3542-
NoConstraints = 1 << 1, // Don't obtain type variable constraints
3543-
Completion = 1 << 2, // Obtaining constraint type for completion
3547+
None = 0,
3548+
Signature = 1 << 0, // Obtaining contextual signature
3549+
NoConstraints = 1 << 1, // Don't obtain type variable constraints
3550+
BaseConstraint = 1 << 2, // Use base constraint type for completions
35443551
}
35453552

35463553
// NOTE: If modifying this enum, must modify `TypeFormatFlags` too!

src/compiler/utilities.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2433,6 +2433,10 @@ namespace ts {
24332433
return (node as ParameterDeclaration).dotDotDotToken !== undefined || !!type && type.kind === SyntaxKind.JSDocVariadicType;
24342434
}
24352435

2436+
export function hasTypeArguments(node: Node): node is HasTypeArguments {
2437+
return !!(node as HasTypeArguments).typeArguments;
2438+
}
2439+
24362440
export const enum AssignmentKind {
24372441
None, Definite, Compound
24382442
}

src/services/completions.ts

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1279,7 +1279,7 @@ namespace ts.Completions {
12791279
// Cursor is inside a JSX self-closing element or opening element
12801280
const attrsType = jsxContainer && typeChecker.getContextualType(jsxContainer.attributes);
12811281
if (!attrsType) return GlobalsSearch.Continue;
1282-
symbols = filterJsxAttributes(getPropertiesForObjectExpression(attrsType, jsxContainer!.attributes, typeChecker), jsxContainer!.attributes.properties);
1282+
symbols = filterJsxAttributes(getPropertiesForObjectExpression(attrsType, /*baseType*/ undefined, jsxContainer!.attributes, typeChecker), jsxContainer!.attributes.properties);
12831283
setSortTextToOptionalMember();
12841284
completionKind = CompletionKind.MemberLike;
12851285
isNewIdentifierLocation = false;
@@ -1798,10 +1798,11 @@ namespace ts.Completions {
17981798
let existingMembers: readonly Declaration[] | undefined;
17991799

18001800
if (objectLikeContainer.kind === SyntaxKind.ObjectLiteralExpression) {
1801-
const typeForObject = typeChecker.getContextualType(objectLikeContainer, ContextFlags.Completion);
1802-
if (!typeForObject) return GlobalsSearch.Fail;
1803-
isNewIdentifierLocation = hasIndexSignature(typeForObject);
1804-
typeMembers = getPropertiesForObjectExpression(typeForObject, objectLikeContainer, typeChecker);
1801+
const instantiatedType = typeChecker.getContextualType(objectLikeContainer);
1802+
const baseType = instantiatedType && typeChecker.getContextualType(objectLikeContainer, ContextFlags.BaseConstraint);
1803+
if (!instantiatedType || !baseType) return GlobalsSearch.Fail;
1804+
isNewIdentifierLocation = hasIndexSignature(instantiatedType || baseType);
1805+
typeMembers = getPropertiesForObjectExpression(instantiatedType, baseType, objectLikeContainer, typeChecker);
18051806
existingMembers = objectLikeContainer.properties;
18061807
}
18071808
else {
@@ -2538,15 +2539,19 @@ namespace ts.Completions {
25382539
return jsdoc && jsdoc.tags && (rangeContainsPosition(jsdoc, position) ? findLast(jsdoc.tags, tag => tag.pos < position) : undefined);
25392540
}
25402541

2541-
function getPropertiesForObjectExpression(contextualType: Type, obj: ObjectLiteralExpression | JsxAttributes, checker: TypeChecker): Symbol[] {
2542-
return contextualType.isUnion()
2543-
? checker.getAllPossiblePropertiesOfTypes(contextualType.types.filter(memberType =>
2542+
function getPropertiesForObjectExpression(contextualType: Type, baseConstrainedType: Type | undefined, obj: ObjectLiteralExpression | JsxAttributes, checker: TypeChecker): Symbol[] {
2543+
const type = baseConstrainedType && !(baseConstrainedType.flags & TypeFlags.AnyOrUnknown)
2544+
? checker.getUnionType([contextualType, baseConstrainedType])
2545+
: contextualType;
2546+
2547+
return type.isUnion()
2548+
? checker.getAllPossiblePropertiesOfTypes(type.types.filter(memberType =>
25442549
// 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.
25452550
!(memberType.flags & TypeFlags.Primitive ||
25462551
checker.isArrayLikeType(memberType) ||
25472552
typeHasCallOrConstructSignatures(memberType, checker) ||
25482553
checker.isTypeInvalidDueToUnionDiscriminant(memberType, obj))))
2549-
: contextualType.getApparentProperties();
2554+
: type.getApparentProperties();
25502555
}
25512556

25522557
/**

tests/baselines/reference/api/tsserverlibrary.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,7 @@ declare namespace ts {
502502
}
503503
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;
504504
export type HasType = SignatureDeclaration | VariableDeclaration | ParameterDeclaration | PropertySignature | PropertyDeclaration | TypePredicateNode | ParenthesizedTypeNode | TypeOperatorNode | MappedTypeNode | AssertionExpression | TypeAliasDeclaration | JSDocTypeExpression | JSDocNonNullableType | JSDocNullableType | JSDocOptionalType | JSDocVariadicType;
505+
export type HasTypeArguments = CallExpression | NewExpression | TaggedTemplateExpression | JsxOpeningElement | JsxSelfClosingElement;
505506
export type HasInitializer = HasExpressionInitializer | ForStatement | ForInStatement | ForOfStatement | JsxAttribute;
506507
export type HasExpressionInitializer = VariableDeclaration | ParameterDeclaration | BindingElement | PropertySignature | PropertyDeclaration | PropertyAssignment | EnumMember;
507508
export interface NodeArray<T extends Node> extends ReadonlyArray<T>, TextRange {

tests/baselines/reference/api/typescript.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,7 @@ declare namespace ts {
502502
}
503503
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;
504504
export type HasType = SignatureDeclaration | VariableDeclaration | ParameterDeclaration | PropertySignature | PropertyDeclaration | TypePredicateNode | ParenthesizedTypeNode | TypeOperatorNode | MappedTypeNode | AssertionExpression | TypeAliasDeclaration | JSDocTypeExpression | JSDocNonNullableType | JSDocNullableType | JSDocOptionalType | JSDocVariadicType;
505+
export type HasTypeArguments = CallExpression | NewExpression | TaggedTemplateExpression | JsxOpeningElement | JsxSelfClosingElement;
505506
export type HasInitializer = HasExpressionInitializer | ForStatement | ForInStatement | ForOfStatement | JsxAttribute;
506507
export type HasExpressionInitializer = VariableDeclaration | ParameterDeclaration | BindingElement | PropertySignature | PropertyDeclaration | PropertyAssignment | EnumMember;
507508
export interface NodeArray<T extends Node> extends ReadonlyArray<T>, TextRange {
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////declare function f<T extends string>(
4+
//// p: { a: T extends 'foo' ? { x: string } : { y: string } }
5+
////): void;
6+
////
7+
////f<'foo'>({ a: { /*1*/ } });
8+
////f<string>({ a: { /*2*/ } });
9+
10+
verify.completions({
11+
marker: '1',
12+
exact: [{
13+
name: 'x'
14+
}]
15+
});
16+
17+
verify.completions({
18+
marker: '2',
19+
exact: [{
20+
name: 'y'
21+
}]
22+
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
// #34825
4+
5+
////interface Sample {
6+
//// addBook: { name: string, year: number }
7+
////}
8+
////
9+
////export declare function testIt<T>(method: T[keyof T]): any
10+
////testIt<Sample>({ /**/ });
11+
12+
verify.completions({
13+
marker: '',
14+
exact: [
15+
{ name: 'name' },
16+
{ name: 'year' },
17+
]
18+
});
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
// #34825
4+
5+
////export type GetMethodsForType<T, G extends string> = { [K in keyof T]:
6+
//// T[K] extends () => any ? { name: K, group: G, } : T[K] extends (s: infer U) => any ? { name: K, group: G, payload: U } : never }[keyof T];
7+
////
8+
////
9+
////class Sample {
10+
//// count = 0;
11+
//// books: { name: string, year: number }[] = []
12+
//// increment() {
13+
//// this.count++
14+
//// this.count++
15+
//// }
16+
////
17+
//// addBook(book: Sample["books"][0]) {
18+
//// this.books.push(book)
19+
//// }
20+
////}
21+
////export declare function testIt<T, G extends string>(): (input: any, method: GetMethodsForType<T, G>) => any
22+
////
23+
////
24+
////const t = testIt<Sample, "Sample">()
25+
////
26+
////const i = t(null, { name: "addBook", group: "Sample", payload: { /**/ } })
27+
28+
verify.completions({
29+
marker: '',
30+
exact: [
31+
{ name: 'name' },
32+
{ name: 'year' },
33+
]
34+
});

0 commit comments

Comments
 (0)