Skip to content

Commit 9735fb3

Browse files
committed
Slightly better I guess
1 parent 5f94aae commit 9735fb3

File tree

5 files changed

+58
-24
lines changed

5 files changed

+58
-24
lines changed

src/compiler/checker.ts

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20981,18 +20981,9 @@ namespace ts {
2098120981
if (isJsxOpeningLikeElement(callTarget) && argIndex === 0) {
2098220982
return getEffectiveFirstArgumentForJsxSignature(signature, callTarget);
2098320983
}
20984-
if (contextFlags && contextFlags & ContextFlags.Completion && signature.target) {
20984+
if (contextFlags && contextFlags & ContextFlags.BaseConstraint && signature.target && !tryCast(callTarget, hasTypeArguments)?.typeArguments) {
2098520985
const baseSignature = getBaseSignature(signature.target);
20986-
// Only consider the type from the base signature for completions
20987-
// if it actually contributes something. The type from the contextually
20988-
// instantiated signature is the _real_ type anyway, so we should never
20989-
// let the base type _remove_ completions from the list.
20990-
const baseArgumentType = filterType(
20991-
getTypeAtPosition(baseSignature, argIndex),
20992-
t => !(t.flags & (TypeFlags.AnyOrUnknown | TypeFlags.IndexedAccess)));
20993-
if (!(baseArgumentType.flags & TypeFlags.Never)) {
20994-
return intersectTypes(getTypeAtPosition(signature, argIndex), baseArgumentType);
20995-
}
20986+
return getTypeAtPosition(baseSignature, argIndex);
2099620987
}
2099720988
return getTypeAtPosition(signature, argIndex);
2099820989
}

src/compiler/types.ts

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

711+
export type HasTypeArguments =
712+
| CallExpression
713+
| NewExpression
714+
| TaggedTemplateExpression
715+
| JsxOpeningElement
716+
| JsxSelfClosingElement;
717+
711718
export type HasInitializer =
712719
| HasExpressionInitializer
713720
| ForStatement
@@ -3535,10 +3542,10 @@ namespace ts {
35353542

35363543
/* @internal */
35373544
export const enum ContextFlags {
3538-
None = 0,
3539-
Signature = 1 << 0, // Obtaining contextual signature
3540-
NoConstraints = 1 << 1, // Don't obtain type variable constraints
3541-
Completion = 1 << 2, // Obtaining constraint type for completion
3545+
None = 0,
3546+
Signature = 1 << 0, // Obtaining contextual signature
3547+
NoConstraints = 1 << 1, // Don't obtain type variable constraints
3548+
BaseConstraint = 1 << 2, // Use base constraint type for completions
35423549
}
35433550

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

src/compiler/utilities.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7295,6 +7295,15 @@ namespace ts {
72957295
return !!(node as HasType).type;
72967296
}
72977297

7298+
/** @internal */
7299+
export function hasTypeArguments(node: Node): node is HasTypeArguments {
7300+
return node.kind === SyntaxKind.CallExpression
7301+
|| node.kind === SyntaxKind.NewExpression
7302+
|| node.kind === SyntaxKind.TaggedTemplateExpression
7303+
|| node.kind === SyntaxKind.JsxOpeningElement
7304+
|| node.kind === SyntaxKind.JsxSelfClosingElement;
7305+
}
7306+
72987307
/** True if has initializer node attached to it. */
72997308
/* @internal */
73007309
export function hasInitializer(node: Node): node is HasInitializer {

src/services/completions.ts

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1273,7 +1273,7 @@ namespace ts.Completions {
12731273
// Cursor is inside a JSX self-closing element or opening element
12741274
const attrsType = jsxContainer && typeChecker.getContextualType(jsxContainer.attributes);
12751275
if (!attrsType) return GlobalsSearch.Continue;
1276-
symbols = filterJsxAttributes(getPropertiesForObjectExpression(attrsType, jsxContainer!.attributes, typeChecker), jsxContainer!.attributes.properties);
1276+
symbols = filterJsxAttributes(getPropertiesForObjectExpression(attrsType, /*baseType*/ undefined, jsxContainer!.attributes, typeChecker), jsxContainer!.attributes.properties);
12771277
setSortTextToOptionalMember();
12781278
completionKind = CompletionKind.MemberLike;
12791279
isNewIdentifierLocation = false;
@@ -1780,10 +1780,11 @@ namespace ts.Completions {
17801780
let existingMembers: readonly Declaration[] | undefined;
17811781

17821782
if (objectLikeContainer.kind === SyntaxKind.ObjectLiteralExpression) {
1783-
const typeForObject = typeChecker.getContextualType(objectLikeContainer, ContextFlags.Completion);
1784-
if (!typeForObject) return GlobalsSearch.Fail;
1785-
isNewIdentifierLocation = hasIndexSignature(typeForObject);
1786-
typeMembers = getPropertiesForObjectExpression(typeForObject, objectLikeContainer, typeChecker);
1783+
const instantiatedType = typeChecker.getContextualType(objectLikeContainer);
1784+
const baseType = instantiatedType && typeChecker.getContextualType(objectLikeContainer, ContextFlags.BaseConstraint);
1785+
if (!instantiatedType || !baseType) return GlobalsSearch.Fail;
1786+
isNewIdentifierLocation = hasIndexSignature(instantiatedType || baseType);
1787+
typeMembers = getPropertiesForObjectExpression(instantiatedType, baseType, objectLikeContainer, typeChecker);
17871788
existingMembers = objectLikeContainer.properties;
17881789
}
17891790
else {
@@ -2514,15 +2515,19 @@ namespace ts.Completions {
25142515
return jsdoc && jsdoc.tags && (rangeContainsPosition(jsdoc, position) ? findLast(jsdoc.tags, tag => tag.pos < position) : undefined);
25152516
}
25162517

2517-
function getPropertiesForObjectExpression(contextualType: Type, obj: ObjectLiteralExpression | JsxAttributes, checker: TypeChecker): Symbol[] {
2518-
return contextualType.isUnion()
2519-
? checker.getAllPossiblePropertiesOfTypes(contextualType.types.filter(memberType =>
2518+
function getPropertiesForObjectExpression(contextualType: Type, baseConstrainedType: Type | undefined, obj: ObjectLiteralExpression | JsxAttributes, checker: TypeChecker): Symbol[] {
2519+
const type = baseConstrainedType && !(baseConstrainedType.flags & TypeFlags.AnyOrUnknown)
2520+
? checker.getUnionType([contextualType, baseConstrainedType])
2521+
: contextualType;
2522+
2523+
return type.isUnion()
2524+
? checker.getAllPossiblePropertiesOfTypes(type.types.filter(memberType =>
25202525
// 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.
25212526
!(memberType.flags & TypeFlags.Primitive ||
25222527
checker.isArrayLikeType(memberType) ||
25232528
typeHasCallOrConstructSignatures(memberType, checker) ||
25242529
checker.isTypeInvalidDueToUnionDiscriminant(memberType, obj))))
2525-
: contextualType.getApparentProperties();
2530+
: type.getApparentProperties();
25262531
}
25272532

25282533
/**
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+
});

0 commit comments

Comments
 (0)