From d08ae97ae0db20f84a813c41bcdc2ff390dd3677 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Wed, 22 Jan 2020 17:53:52 -0800 Subject: [PATCH 1/8] Conditionally elide a parameter from contextual type signature calculation --- src/compiler/checker.ts | 19 +++++++++++++++++-- src/compiler/types.ts | 2 ++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 52cd728e7f733..479829ac70184 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -21340,8 +21340,23 @@ namespace ts { // If we're already in the process of resolving the given signature, don't resolve again as // that could cause infinite recursion. Instead, return anySignature. let signature = getNodeLinks(callTarget).resolvedSignature === resolvingSignature ? resolvingSignature : getResolvedSignature(callTarget); - if (contextFlags && contextFlags & ContextFlags.BaseConstraint && signature.target && !hasTypeArguments(callTarget)) { - signature = getBaseSignature(signature.target); + if (contextFlags && contextFlags & ContextFlags.BaseConstraint && signature !== resolvingSignature && !hasTypeArguments(callTarget)) { + if (isCallOrNewExpression(callTarget) && callTarget.arguments) { + let clone = signature.omittedParameterCache && signature.omittedParameterCache[argIndex]; + if (!clone) { + // clone the ast, eliding the target argument + clone = getSynthesizedClone(callTarget) as CallExpression | NewExpression; + (clone as any).id = undefined; + clone.arguments = createNodeArray([...clone.arguments!.slice(0, argIndex), createOmittedExpression(), ...clone.arguments!.slice(argIndex + 1)]); + clone.arguments![argIndex].parent = clone; + clone.parent = callTarget.parent; + (signature.omittedParameterCache || (signature.omittedParameterCache = []))[argIndex] = clone; + } + signature = getResolvedSignature(clone); + } + else if (signature.target) { + signature = getBaseSignature(signature.target); + } } if (isJsxOpeningLikeElement(callTarget) && argIndex === 0) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 1009e75fa89c8..f7fc620b1c950 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4789,6 +4789,8 @@ namespace ts { isolatedSignatureType?: ObjectType; // A manufactured type that just contains the signature for purposes of signature comparison /* @internal */ instantiations?: Map; // Generic signature instantiation cache + /* @internal */ + omittedParameterCache?: CallLikeExpression[]; } export const enum IndexKind { From d4848aa4dfd19bd7bf900c6629955ab8d936a0b8 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Thu, 23 Jan 2020 12:49:48 -0800 Subject: [PATCH 2/8] Slightly different approach to forbid inference to specific expressions --- src/compiler/checker.ts | 61 +++++++++++++++++++++++------------------ src/compiler/types.ts | 1 + 2 files changed, 36 insertions(+), 26 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 479829ac70184..5f6f099f0edb8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -468,7 +468,26 @@ namespace ts { getRootSymbols, getContextualType: (nodeIn: Expression, contextFlags?: ContextFlags) => { const node = getParseTreeNode(nodeIn, isExpression); - return node ? getContextualType(node, contextFlags) : undefined; + if (!node) { + return undefined; + } + const links = getNodeLinks(node); + const resolvedType = links.resolvedType; + const skipDirectInference = links.skipDirectInference; + const containingCall = findAncestor(node, isCallLikeExpression); + const containingCallResolvedSignature = containingCall && getNodeLinks(containingCall).resolvedSignature; + if (contextFlags! & ContextFlags.BaseConstraint && containingCall) { + links.resolvedType = undefined; + links.skipDirectInference = true; + getNodeLinks(containingCall).resolvedSignature = undefined; + } + const result = getContextualType(node, contextFlags); + if (contextFlags! & ContextFlags.BaseConstraint && containingCall) { + links.skipDirectInference = skipDirectInference; + links.resolvedType = resolvedType; + getNodeLinks(containingCall).resolvedSignature = containingCallResolvedSignature; + } + return result; }, getContextualTypeForObjectLiteralElement: nodeIn => { const node = getParseTreeNode(nodeIn, isObjectLiteralElementLike); @@ -17675,6 +17694,14 @@ namespace ts { undefined; } + function hasSkipDirectInferenceFlag(node: Node) { + return !!getNodeLinks(node).skipDirectInference; + } + + function isFromInferenceBlockedSource(type: Type) { + return !!(type.symbol && some(type.symbol.declarations, hasSkipDirectInferenceFlag)); + } + function inferTypes(inferences: InferenceInfo[], originalSource: Type, originalTarget: Type, priority: InferencePriority = 0, contravariant = false) { let symbolStack: Symbol[]; let visited: Map; @@ -17765,7 +17792,7 @@ namespace ts { // of inference. Also, we exclude inferences for silentNeverType (which is used as a wildcard // when constructing types from type parameters that had no inference candidates). if (getObjectFlags(source) & ObjectFlags.NonInferrableType || source === nonInferrableAnyType || source === silentNeverType || - (priority & InferencePriority.ReturnType && (source === autoType || source === autoArrayType))) { + (priority & InferencePriority.ReturnType && (source === autoType || source === autoArrayType)) || isFromInferenceBlockedSource(source)) { return; } const inference = getInferenceInfoForType(target); @@ -21330,34 +21357,16 @@ namespace ts { } // In a typed function call, an argument or substitution expression is contextually typed by the type of the corresponding parameter. - function getContextualTypeForArgument(callTarget: CallLikeExpression, arg: Expression, contextFlags?: ContextFlags): Type | undefined { + function getContextualTypeForArgument(callTarget: CallLikeExpression, arg: Expression): Type | undefined { const args = getEffectiveCallArguments(callTarget); const argIndex = args.indexOf(arg); // -1 for e.g. the expression of a CallExpression, or the tag of a TaggedTemplateExpression - return argIndex === -1 ? undefined : getContextualTypeForArgumentAtIndex(callTarget, argIndex, contextFlags); + return argIndex === -1 ? undefined : getContextualTypeForArgumentAtIndex(callTarget, argIndex); } - function getContextualTypeForArgumentAtIndex(callTarget: CallLikeExpression, argIndex: number, contextFlags?: ContextFlags): Type { + function getContextualTypeForArgumentAtIndex(callTarget: CallLikeExpression, argIndex: number): Type { // If we're already in the process of resolving the given signature, don't resolve again as // that could cause infinite recursion. Instead, return anySignature. - let signature = getNodeLinks(callTarget).resolvedSignature === resolvingSignature ? resolvingSignature : getResolvedSignature(callTarget); - if (contextFlags && contextFlags & ContextFlags.BaseConstraint && signature !== resolvingSignature && !hasTypeArguments(callTarget)) { - if (isCallOrNewExpression(callTarget) && callTarget.arguments) { - let clone = signature.omittedParameterCache && signature.omittedParameterCache[argIndex]; - if (!clone) { - // clone the ast, eliding the target argument - clone = getSynthesizedClone(callTarget) as CallExpression | NewExpression; - (clone as any).id = undefined; - clone.arguments = createNodeArray([...clone.arguments!.slice(0, argIndex), createOmittedExpression(), ...clone.arguments!.slice(argIndex + 1)]); - clone.arguments![argIndex].parent = clone; - clone.parent = callTarget.parent; - (signature.omittedParameterCache || (signature.omittedParameterCache = []))[argIndex] = clone; - } - signature = getResolvedSignature(clone); - } - else if (signature.target) { - signature = getBaseSignature(signature.target); - } - } + const signature = getNodeLinks(callTarget).resolvedSignature === resolvingSignature ? resolvingSignature : getResolvedSignature(callTarget); if (isJsxOpeningLikeElement(callTarget) && argIndex === 0) { return getEffectiveFirstArgumentForJsxSignature(signature, callTarget); @@ -21753,7 +21762,7 @@ namespace ts { } /* falls through */ case SyntaxKind.NewExpression: - return getContextualTypeForArgument(parent, node, contextFlags); + return getContextualTypeForArgument(parent, node); case SyntaxKind.TypeAssertionExpression: case SyntaxKind.AsExpression: return isConstTypeReference((parent).type) ? undefined : getTypeFromTypeNode((parent).type); @@ -21803,7 +21812,7 @@ namespace ts { // (as below) instead! return node.parent.contextualType; } - return getContextualTypeForArgumentAtIndex(node, 0, contextFlags); + return getContextualTypeForArgumentAtIndex(node, 0); } function getEffectiveFirstArgumentForJsxSignature(signature: Signature, node: JsxOpeningLikeElement) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index f7fc620b1c950..b642aeacca1ef 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4240,6 +4240,7 @@ namespace ts { outerTypeParameters?: TypeParameter[]; // Outer type parameters of anonymous object type instantiations?: Map; // Instantiations of generic type alias (undefined if non-generic) isExhaustive?: boolean; // Is node an exhaustive switch statement + skipDirectInference?: true; // Flag set by the API `getContextualType` call on a node when `BaseConstraint` is passed to force the checker to skip making inferences to a node's type } export const enum TypeFlags { From e4cf72f652d18f3e04bce9f28f572d74368c0420 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Thu, 30 Jan 2020 15:50:39 -0800 Subject: [PATCH 3/8] Handle nested literals and mapped types correctly --- src/compiler/checker.ts | 19 +++--- ...tionsObjectLiteralWithPartialConstraint.ts | 64 +++++++++++++++++++ 2 files changed, 75 insertions(+), 8 deletions(-) create mode 100644 tests/cases/fourslash/completionsObjectLiteralWithPartialConstraint.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0fd48814976c4..cbfc028ccb881 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -471,20 +471,23 @@ namespace ts { if (!node) { return undefined; } - const links = getNodeLinks(node); - const resolvedType = links.resolvedType; - const skipDirectInference = links.skipDirectInference; const containingCall = findAncestor(node, isCallLikeExpression); const containingCallResolvedSignature = containingCall && getNodeLinks(containingCall).resolvedSignature; if (contextFlags! & ContextFlags.BaseConstraint && containingCall) { - links.resolvedType = undefined; - links.skipDirectInference = true; + let toMarkSkip = node as Node; + do { + getNodeLinks(toMarkSkip).skipDirectInference = true; + toMarkSkip = toMarkSkip.parent; + } while (toMarkSkip && toMarkSkip !== containingCall); getNodeLinks(containingCall).resolvedSignature = undefined; } const result = getContextualType(node, contextFlags); if (contextFlags! & ContextFlags.BaseConstraint && containingCall) { - links.skipDirectInference = skipDirectInference; - links.resolvedType = resolvedType; + let toMarkSkip = node as Node; + do { + getNodeLinks(toMarkSkip).skipDirectInference = undefined; + toMarkSkip = toMarkSkip.parent; + } while (toMarkSkip && toMarkSkip !== containingCall); getNodeLinks(containingCall).resolvedSignature = containingCallResolvedSignature; } return result; @@ -18218,7 +18221,7 @@ namespace ts { // type and then make a secondary inference from that type to T. We make a secondary inference // such that direct inferences to T get priority over inferences to Partial, for example. const inference = getInferenceInfoForType((constraintType).type); - if (inference && !inference.isFixed) { + if (inference && !inference.isFixed && !isFromInferenceBlockedSource(source)) { const inferredType = inferTypeForHomomorphicMappedType(source, target, constraintType); if (inferredType) { // We assign a lower priority to inferences made from types containing non-inferrable diff --git a/tests/cases/fourslash/completionsObjectLiteralWithPartialConstraint.ts b/tests/cases/fourslash/completionsObjectLiteralWithPartialConstraint.ts new file mode 100644 index 0000000000000..ebd47f3d9a995 --- /dev/null +++ b/tests/cases/fourslash/completionsObjectLiteralWithPartialConstraint.ts @@ -0,0 +1,64 @@ +/// + +////interface MyOptions { +//// hello?: boolean; +//// world?: boolean; +////} +////declare function bar(options?: Partial): void; +////bar({ hello: true, /*1*/ }); +//// +////interface Test { +//// keyPath?: string; +//// autoIncrement?: boolean; +////} +//// +////function test>(opt: T) { } +//// +////test({ +//// a: { +//// keyPath: 'x.y', +//// autoIncrement: true +//// }, +//// b: { +//// /*2*/ +//// } +////}); +////type Colors = { +//// rgb: { r: number, g: number, b: number }; +//// hsl: { h: number, s: number, l: number } +////}; +//// +////function createColor(kind: T, values: Colors[T]) { } +//// +////createColor('rgb', { +//// /*3*/ +////}); +//// +////declare function f(x: T, y: { a: U, b: V }[T]): void; +//// +////f('a', { +//// /*4*/ +////}); +//// +////declare function f2(x: T): void; +////f2({ +//// /*5*/ +////}); +//// +////type X = { a: { a }, b: { b } } +//// +////function f4(p: { kind: T } & X[T]) { } +//// +////f4({ +//// kind: "a", +//// /*6*/ +////}) + +verify.completions( + { marker: "1", exact: [{ name: "world", sortText: completion.SortText.OptionalMember }] }, + { marker: "2", exact: [{ name: "keyPath", sortText: completion.SortText.OptionalMember }, { name: "autoIncrement", sortText: completion.SortText.OptionalMember }] }, + { marker: "3", exact: ["r", "g", "b"] }, + { marker: "4", exact: [{ name: "a", sortText: completion.SortText.OptionalMember }] }, + { marker: "5", exact: [{ name: "x", sortText: completion.SortText.OptionalMember }] }, + { marker: "6", exact: ["a"] }, +); \ No newline at end of file From e7793c1175c6b524eaf07e7bf32d731edf4a1305 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Thu, 30 Jan 2020 15:52:03 -0800 Subject: [PATCH 4/8] Delete unused cache --- src/compiler/types.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 95d452d85f5a3..1191d541a1546 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4799,8 +4799,6 @@ namespace ts { isolatedSignatureType?: ObjectType; // A manufactured type that just contains the signature for purposes of signature comparison /* @internal */ instantiations?: Map; // Generic signature instantiation cache - /* @internal */ - omittedParameterCache?: CallLikeExpression[]; } export const enum IndexKind { From 1a011612a09cebfaa7934aefd196c798eb11dfb5 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Fri, 31 Jan 2020 13:49:49 -0800 Subject: [PATCH 5/8] Rename ContextFlags.BaseConstraint and related usage --- src/compiler/checker.ts | 6 +++--- src/compiler/types.ts | 2 +- src/services/completions.ts | 22 +++++++++++----------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index cbfc028ccb881..6db1d646e72b6 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -473,7 +473,7 @@ namespace ts { } const containingCall = findAncestor(node, isCallLikeExpression); const containingCallResolvedSignature = containingCall && getNodeLinks(containingCall).resolvedSignature; - if (contextFlags! & ContextFlags.BaseConstraint && containingCall) { + if (contextFlags! & ContextFlags.Completions && containingCall) { let toMarkSkip = node as Node; do { getNodeLinks(toMarkSkip).skipDirectInference = true; @@ -482,7 +482,7 @@ namespace ts { getNodeLinks(containingCall).resolvedSignature = undefined; } const result = getContextualType(node, contextFlags); - if (contextFlags! & ContextFlags.BaseConstraint && containingCall) { + if (contextFlags! & ContextFlags.Completions && containingCall) { let toMarkSkip = node as Node; do { getNodeLinks(toMarkSkip).skipDirectInference = undefined; @@ -21929,7 +21929,7 @@ namespace ts { } function getContextualJsxElementAttributesType(node: JsxOpeningLikeElement, contextFlags?: ContextFlags) { - if (isJsxOpeningElement(node) && node.parent.contextualType && contextFlags !== ContextFlags.BaseConstraint) { + if (isJsxOpeningElement(node) && node.parent.contextualType && contextFlags !== ContextFlags.Completions) { // Contextually applied type is moved from attributes up to the outer jsx attributes so when walking up from the children they get hit // _However_ to hit them from the _attributes_ we must look for them here; otherwise we'll used the declared type // (as below) instead! diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 1191d541a1546..2eb5d9a7c0a08 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3611,7 +3611,7 @@ namespace ts { None = 0, Signature = 1 << 0, // Obtaining contextual signature NoConstraints = 1 << 1, // Don't obtain type variable constraints - BaseConstraint = 1 << 2, // Use base constraint type for completions + Completions = 1 << 2, // Ignore inference to current node for completions } // NOTE: If modifying this enum, must modify `TypeFormatFlags` too! diff --git a/src/services/completions.ts b/src/services/completions.ts index 2d2ee7c328b89..2d34fea0fdd83 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -1284,8 +1284,8 @@ namespace ts.Completions { // Cursor is inside a JSX self-closing element or opening element const attrsType = jsxContainer && typeChecker.getContextualType(jsxContainer.attributes); if (!attrsType) return GlobalsSearch.Continue; - const baseType = jsxContainer && typeChecker.getContextualType(jsxContainer.attributes, ContextFlags.BaseConstraint); - symbols = filterJsxAttributes(getPropertiesForObjectExpression(attrsType, baseType, jsxContainer!.attributes, typeChecker), jsxContainer!.attributes.properties); + const completionsType = jsxContainer && typeChecker.getContextualType(jsxContainer.attributes, ContextFlags.Completions); + symbols = filterJsxAttributes(getPropertiesForObjectExpression(attrsType, completionsType, jsxContainer!.attributes, typeChecker), jsxContainer!.attributes.properties); setSortTextToOptionalMember(); completionKind = CompletionKind.MemberLike; isNewIdentifierLocation = false; @@ -1804,10 +1804,10 @@ namespace ts.Completions { if (objectLikeContainer.kind === SyntaxKind.ObjectLiteralExpression) { const instantiatedType = typeChecker.getContextualType(objectLikeContainer); - const baseType = instantiatedType && typeChecker.getContextualType(objectLikeContainer, ContextFlags.BaseConstraint); - if (!instantiatedType || !baseType) return GlobalsSearch.Fail; - isNewIdentifierLocation = hasIndexSignature(instantiatedType || baseType); - typeMembers = getPropertiesForObjectExpression(instantiatedType, baseType, objectLikeContainer, typeChecker); + const completionsType = instantiatedType && typeChecker.getContextualType(objectLikeContainer, ContextFlags.Completions); + if (!instantiatedType || !completionsType) return GlobalsSearch.Fail; + isNewIdentifierLocation = hasIndexSignature(instantiatedType || completionsType); + typeMembers = getPropertiesForObjectExpression(instantiatedType, completionsType, objectLikeContainer, typeChecker); existingMembers = objectLikeContainer.properties; } else { @@ -2553,10 +2553,10 @@ namespace ts.Completions { return jsdoc && jsdoc.tags && (rangeContainsPosition(jsdoc, position) ? findLast(jsdoc.tags, tag => tag.pos < position) : undefined); } - function getPropertiesForObjectExpression(contextualType: Type, baseConstrainedType: Type | undefined, obj: ObjectLiteralExpression | JsxAttributes, checker: TypeChecker): Symbol[] { - const hasBaseType = baseConstrainedType && baseConstrainedType !== contextualType; - const type = hasBaseType && !(baseConstrainedType!.flags & TypeFlags.AnyOrUnknown) - ? checker.getUnionType([contextualType, baseConstrainedType!]) + function getPropertiesForObjectExpression(contextualType: Type, completionsType: Type | undefined, obj: ObjectLiteralExpression | JsxAttributes, checker: TypeChecker): Symbol[] { + const hasCompletionsType = completionsType && completionsType !== contextualType; + const type = hasCompletionsType && !(completionsType!.flags & TypeFlags.AnyOrUnknown) + ? checker.getUnionType([contextualType, completionsType!]) : contextualType; const properties = type.isUnion() @@ -2568,7 +2568,7 @@ namespace ts.Completions { checker.isTypeInvalidDueToUnionDiscriminant(memberType, obj)))) : type.getApparentProperties(); - return hasBaseType ? properties.filter(hasDeclarationOtherThanSelf) : properties; + return hasCompletionsType ? properties.filter(hasDeclarationOtherThanSelf) : properties; // Filter out members whose only declaration is the object literal itself to avoid // self-fulfilling completions like: From 873e28756030418fed310151cbc271124b0e4274 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Fri, 31 Jan 2020 13:52:50 -0800 Subject: [PATCH 6/8] Add tests from my PR --- .../completionsGenericIndexedAccess3.ts | 35 +++++++++++++++ .../completionsGenericIndexedAccess4.ts | 45 +++++++++++++++++++ .../completionsGenericIndexedAccess5.ts | 28 ++++++++++++ .../completionsGenericIndexedAccess6.ts | 23 ++++++++++ 4 files changed, 131 insertions(+) create mode 100644 tests/cases/fourslash/completionsGenericIndexedAccess3.ts create mode 100644 tests/cases/fourslash/completionsGenericIndexedAccess4.ts create mode 100644 tests/cases/fourslash/completionsGenericIndexedAccess5.ts create mode 100644 tests/cases/fourslash/completionsGenericIndexedAccess6.ts diff --git a/tests/cases/fourslash/completionsGenericIndexedAccess3.ts b/tests/cases/fourslash/completionsGenericIndexedAccess3.ts new file mode 100644 index 0000000000000..730cdd8f631dd --- /dev/null +++ b/tests/cases/fourslash/completionsGenericIndexedAccess3.ts @@ -0,0 +1,35 @@ +/// + +////interface CustomElements { +//// 'component-one': { +//// foo?: string; +//// }, +//// 'component-two': { +//// bar?: string; +//// } +////} +//// +////interface Options { +//// props: CustomElements[T]; +////} +//// +////declare function create(name: T, options: Options): void; +//// +////create('component-one', { props: { /*1*/ } }); +////create('component-two', { props: { /*2*/ } }); + +verify.completions({ + marker: "1", + exact: [{ + name: "foo", + sortText: completion.SortText.OptionalMember + }] +}); + +verify.completions({ + marker: "2", + exact: [{ + name: "bar", + sortText: completion.SortText.OptionalMember + }] +}); diff --git a/tests/cases/fourslash/completionsGenericIndexedAccess4.ts b/tests/cases/fourslash/completionsGenericIndexedAccess4.ts new file mode 100644 index 0000000000000..0edeaedf9821f --- /dev/null +++ b/tests/cases/fourslash/completionsGenericIndexedAccess4.ts @@ -0,0 +1,45 @@ +/// + +////interface CustomElements { +//// 'component-one': { +//// foo?: string; +//// }, +//// 'component-two': { +//// bar?: string; +//// } +////} +//// +////interface Options { +//// props: CustomElements[T]; +////} +//// +////declare function create(name: T, options: Options): void; +////declare function create(name: T, options: Options): void; +//// +////create('hello', { props: { /*1*/ } }) +////create('goodbye', { props: { /*2*/ } }) +////create('component-one', { props: { /*3*/ } }); + +verify.completions({ + marker: "1", + exact: [{ + name: "foo", + sortText: completion.SortText.OptionalMember + }] +}); + +verify.completions({ + marker: "2", + exact: [{ + name: "bar", + sortText: completion.SortText.OptionalMember + }] +}); + +verify.completions({ + marker: "3", + exact: [{ + name: "foo", + sortText: completion.SortText.OptionalMember + }] +}); diff --git a/tests/cases/fourslash/completionsGenericIndexedAccess5.ts b/tests/cases/fourslash/completionsGenericIndexedAccess5.ts new file mode 100644 index 0000000000000..7f3a0aa369094 --- /dev/null +++ b/tests/cases/fourslash/completionsGenericIndexedAccess5.ts @@ -0,0 +1,28 @@ +////interface CustomElements { +//// 'component-one': { +//// foo?: string; +//// }, +//// 'component-two': { +//// bar?: string; +//// } +////} +//// +////interface Options { +//// props?: {} & { x: CustomElements[(T extends string ? T : never) & string][] }['x']; +////} +//// +////declare function f(k: T, options: Options): void; +//// +////f("component-one", { +//// props: [{ +//// /**/ +//// }] +////}) + +verify.completions({ + marker: "", + exact: [{ + name: "foo", + sortText: completion.SortText.OptionalMember + }] +}); diff --git a/tests/cases/fourslash/completionsGenericIndexedAccess6.ts b/tests/cases/fourslash/completionsGenericIndexedAccess6.ts new file mode 100644 index 0000000000000..496ebf7b50ecf --- /dev/null +++ b/tests/cases/fourslash/completionsGenericIndexedAccess6.ts @@ -0,0 +1,23 @@ +// @Filename: component.tsx + +////interface CustomElements { +//// 'component-one': { +//// foo?: string; +//// }, +//// 'component-two': { +//// bar?: string; +//// } +////} +//// +////type Options = { kind: T } & Required<{ x: CustomElements[(T extends string ? T : never) & string] }['x']>; +//// +////declare function Component(props: Options): void; +//// +////const c = + +verify.completions({ + marker: "", + exact: [{ + name: "foo" + }] +}) From 27d95a6de22370f1adeb7e126a744a3e7ee88587 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Fri, 31 Jan 2020 15:03:44 -0800 Subject: [PATCH 7/8] Update ContextFlags comment Co-Authored-By: Wesley Wigham --- src/compiler/types.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 2eb5d9a7c0a08..4dedcc99cac46 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3611,7 +3611,8 @@ namespace ts { None = 0, Signature = 1 << 0, // Obtaining contextual signature NoConstraints = 1 << 1, // Don't obtain type variable constraints - Completions = 1 << 2, // Ignore inference to current node for completions + Completions = 1 << 2, // Ignore inference to current node and parent nodes out to the containing call for completions + } // NOTE: If modifying this enum, must modify `TypeFormatFlags` too! From 729f837f39e3d0ea7fe2fc37b3b822841a5d2e12 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Fri, 31 Jan 2020 15:04:54 -0800 Subject: [PATCH 8/8] Update comments and fourslash triple slash refs --- src/compiler/types.ts | 2 +- tests/cases/fourslash/completionsGenericIndexedAccess5.ts | 2 ++ tests/cases/fourslash/completionsGenericIndexedAccess6.ts | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 4dedcc99cac46..47a6a95e825f5 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4250,7 +4250,7 @@ namespace ts { outerTypeParameters?: TypeParameter[]; // Outer type parameters of anonymous object type instantiations?: Map; // Instantiations of generic type alias (undefined if non-generic) isExhaustive?: boolean; // Is node an exhaustive switch statement - skipDirectInference?: true; // Flag set by the API `getContextualType` call on a node when `BaseConstraint` is passed to force the checker to skip making inferences to a node's type + skipDirectInference?: true; // Flag set by the API `getContextualType` call on a node when `Completions` is passed to force the checker to skip making inferences to a node's type } export const enum TypeFlags { diff --git a/tests/cases/fourslash/completionsGenericIndexedAccess5.ts b/tests/cases/fourslash/completionsGenericIndexedAccess5.ts index 7f3a0aa369094..70ac6214d88b7 100644 --- a/tests/cases/fourslash/completionsGenericIndexedAccess5.ts +++ b/tests/cases/fourslash/completionsGenericIndexedAccess5.ts @@ -1,3 +1,5 @@ +/// + ////interface CustomElements { //// 'component-one': { //// foo?: string; diff --git a/tests/cases/fourslash/completionsGenericIndexedAccess6.ts b/tests/cases/fourslash/completionsGenericIndexedAccess6.ts index 496ebf7b50ecf..ae77524709393 100644 --- a/tests/cases/fourslash/completionsGenericIndexedAccess6.ts +++ b/tests/cases/fourslash/completionsGenericIndexedAccess6.ts @@ -1,3 +1,5 @@ +/// + // @Filename: component.tsx ////interface CustomElements {