diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9b66295a663ed..cbfc028ccb881 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -468,7 +468,29 @@ namespace ts { getRootSymbols, getContextualType: (nodeIn: Expression, contextFlags?: ContextFlags) => { const node = getParseTreeNode(nodeIn, isExpression); - return node ? getContextualType(node, contextFlags) : undefined; + if (!node) { + return undefined; + } + const containingCall = findAncestor(node, isCallLikeExpression); + const containingCallResolvedSignature = containingCall && getNodeLinks(containingCall).resolvedSignature; + if (contextFlags! & ContextFlags.BaseConstraint && containingCall) { + 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) { + let toMarkSkip = node as Node; + do { + getNodeLinks(toMarkSkip).skipDirectInference = undefined; + toMarkSkip = toMarkSkip.parent; + } while (toMarkSkip && toMarkSkip !== containingCall); + getNodeLinks(containingCall).resolvedSignature = containingCallResolvedSignature; + } + return result; }, getContextualTypeForObjectLiteralElement: nodeIn => { const node = getParseTreeNode(nodeIn, isObjectLiteralElementLike); @@ -17797,6 +17819,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; @@ -17887,7 +17917,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); @@ -18191,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 @@ -21450,19 +21480,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.target && !hasTypeArguments(callTarget)) { - signature = getBaseSignature(signature.target); - } + const signature = getNodeLinks(callTarget).resolvedSignature === resolvingSignature ? resolvingSignature : getResolvedSignature(callTarget); if (isJsxOpeningLikeElement(callTarget) && argIndex === 0) { return getEffectiveFirstArgumentForJsxSignature(signature, callTarget); @@ -21858,7 +21885,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); @@ -21908,7 +21935,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 b5760f7a88e8e..1191d541a1546 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4249,6 +4249,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 { 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