From c13d850e7e2e56a84f7183517cd0b79679143bb2 Mon Sep 17 00:00:00 2001
From: Andrew Branch <andrew@wheream.io>
Date: Fri, 11 Feb 2022 10:11:08 -0800
Subject: [PATCH 1/2] Add failing test

---
 .../cases/fourslash/completionsLiteralOverload.ts  | 14 ++++++++++++++
 1 file changed, 14 insertions(+)
 create mode 100644 tests/cases/fourslash/completionsLiteralOverload.ts

diff --git a/tests/cases/fourslash/completionsLiteralOverload.ts b/tests/cases/fourslash/completionsLiteralOverload.ts
new file mode 100644
index 0000000000000..09486afe1ab62
--- /dev/null
+++ b/tests/cases/fourslash/completionsLiteralOverload.ts
@@ -0,0 +1,14 @@
+/// <reference path="fourslash.ts" />
+
+//// interface Events {
+////   drag: any;
+////   dragenter: any;
+//// }
+//// declare function addListener<K extends keyof Events>(type: K, listener: (ev: Events[K]) => any): void;
+//// declare function addListener(type: string, listener: (ev: any) => any): void;
+////
+//// addListener("/**/");
+
+verify.completions({ marker: "", isNewIdentifierLocation: true, exact: ["drag", "dragenter"] });
+edit.insert("drag");
+verify.completions({ isNewIdentifierLocation: true, exact: ["drag", "dragenter"] });
\ No newline at end of file

From 0d266cd0f0dea867b64540fe1a41e098d20c0d00 Mon Sep 17 00:00:00 2001
From: Andrew Branch <andrew@wheream.io>
Date: Wed, 23 Mar 2022 10:40:12 -0700
Subject: [PATCH 2/2] Fix string literal completions when a partially-typed
 string fixes inference to a type parameter

Ugghhghgh

Revert "Ugghhghgh"

This reverts commit cad98b988bf05b98b91daaf69a148bf92e4a98f7.

FSDjkl;afsdjklsfdjksdfkjjk

Revert "FSDjkl;afsdjklsfdjksdfkjjk"

This reverts commit 0cc19c6bb6c88ad45bcd154ccf13d85a97f7850e.

Revert "Revert "Ugghhghgh""

This reverts commit 44434a34965151375416652928e35a3f8c444020.

It works
---
 src/compiler/checker.ts                       | 90 +++++++++++--------
 src/compiler/types.ts                         |  1 +
 src/harness/fourslashImpl.ts                  | 10 +--
 src/services/stringCompletions.ts             | 19 ++--
 .../fourslash/completionsLiteralOverload.ts   | 17 +++-
 5 files changed, 84 insertions(+), 53 deletions(-)

diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts
index 0d7adfafede23..50382f5b62d84 100644
--- a/src/compiler/checker.ts
+++ b/src/compiler/checker.ts
@@ -175,15 +175,16 @@ namespace ts {
     }
 
     const enum CheckMode {
-        Normal = 0,                        // Normal type checking
-        Contextual = 1 << 0,               // Explicitly assigned contextual type, therefore not cacheable
-        Inferential = 1 << 1,              // Inferential typing
-        SkipContextSensitive = 1 << 2,     // Skip context sensitive function expressions
-        SkipGenericFunctions = 1 << 3,     // Skip single signature generic functions
-        IsForSignatureHelp = 1 << 4,       // Call resolution for purposes of signature help
-        RestBindingElement = 1 << 5,       // Checking a type that is going to be used to determine the type of a rest binding element
-                                           //   e.g. in `const { a, ...rest } = foo`, when checking the type of `foo` to determine the type of `rest`,
-                                           //   we need to preserve generic types instead of substituting them for constraints
+        Normal = 0,                                     // Normal type checking
+        Contextual = 1 << 0,                            // Explicitly assigned contextual type, therefore not cacheable
+        Inferential = 1 << 1,                           // Inferential typing
+        SkipContextSensitive = 1 << 2,                  // Skip context sensitive function expressions
+        SkipGenericFunctions = 1 << 3,                  // Skip single signature generic functions
+        IsForSignatureHelp = 1 << 4,                    // Call resolution for purposes of signature help
+        IsForStringLiteralArgumentCompletions = 1 << 5, // Do not infer from the argument currently being typed
+        RestBindingElement = 1 << 6,                    // Checking a type that is going to be used to determine the type of a rest binding element
+                                                        //   e.g. in `const { a, ...rest } = foo`, when checking the type of `foo` to determine the type of `rest`,
+                                                        //   we need to preserve generic types instead of substituting them for constraints
     }
 
     const enum SignatureCheckMode {
@@ -532,26 +533,10 @@ namespace ts {
                 if (!node) {
                     return undefined;
                 }
-                const containingCall = findAncestor(node, isCallLikeExpression);
-                const containingCallResolvedSignature = containingCall && getNodeLinks(containingCall).resolvedSignature;
-                if (contextFlags! & ContextFlags.Completions && 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.Completions && containingCall) {
-                    let toMarkSkip = node as Node;
-                    do {
-                        getNodeLinks(toMarkSkip).skipDirectInference = undefined;
-                        toMarkSkip = toMarkSkip.parent;
-                    } while (toMarkSkip && toMarkSkip !== containingCall);
-                    getNodeLinks(containingCall).resolvedSignature = containingCallResolvedSignature;
+                if (contextFlags! & ContextFlags.Completions) {
+                    return runWithInferenceBlockedFromSourceNode(node, () => getContextualType(node, contextFlags));
                 }
-                return result;
+                return getContextualType(node, contextFlags);
             },
             getContextualTypeForObjectLiteralElement: nodeIn => {
                 const node = getParseTreeNode(nodeIn, isObjectLiteralElementLike);
@@ -570,6 +555,8 @@ namespace ts {
             getFullyQualifiedName,
             getResolvedSignature: (node, candidatesOutArray, argumentCount) =>
                 getResolvedSignatureWorker(node, candidatesOutArray, argumentCount, CheckMode.Normal),
+            getResolvedSignatureForStringLiteralCompletions: (call, editingArgument, candidatesOutArray) =>
+                getResolvedSignatureWorker(call, candidatesOutArray, /*argumentCount*/ undefined, CheckMode.IsForStringLiteralArgumentCompletions, editingArgument),
             getResolvedSignatureForSignatureHelp: (node, candidatesOutArray, argumentCount) =>
                 getResolvedSignatureWorker(node, candidatesOutArray, argumentCount, CheckMode.IsForSignatureHelp),
             getExpandedParameters,
@@ -739,10 +726,36 @@ namespace ts {
             getMemberOverrideModifierStatus,
         };
 
-        function getResolvedSignatureWorker(nodeIn: CallLikeExpression, candidatesOutArray: Signature[] | undefined, argumentCount: number | undefined, checkMode: CheckMode): Signature | undefined {
+        function runWithInferenceBlockedFromSourceNode<T>(node: Node | undefined, fn: () => T): T {
+            const containingCall = findAncestor(node, isCallLikeExpression);
+            const containingCallResolvedSignature = containingCall && getNodeLinks(containingCall).resolvedSignature;
+            if (containingCall) {
+                let toMarkSkip = node!;
+                do {
+                    getNodeLinks(toMarkSkip).skipDirectInference = true;
+                    toMarkSkip = toMarkSkip.parent;
+                } while (toMarkSkip && toMarkSkip !== containingCall);
+                getNodeLinks(containingCall).resolvedSignature = undefined;
+            }
+            const result = fn();
+            if (containingCall) {
+                let toMarkSkip = node!;
+                do {
+                    getNodeLinks(toMarkSkip).skipDirectInference = undefined;
+                    toMarkSkip = toMarkSkip.parent;
+                } while (toMarkSkip && toMarkSkip !== containingCall);
+                getNodeLinks(containingCall).resolvedSignature = containingCallResolvedSignature;
+            }
+            return result;
+        }
+
+        function getResolvedSignatureWorker(nodeIn: CallLikeExpression, candidatesOutArray: Signature[] | undefined, argumentCount: number | undefined, checkMode: CheckMode, editingArgument?: Node): Signature | undefined {
             const node = getParseTreeNode(nodeIn, isCallLikeExpression);
             apparentArgumentCount = argumentCount;
-            const res = node ? getResolvedSignature(node, candidatesOutArray, checkMode) : undefined;
+            const res =
+                !node ? undefined :
+                editingArgument ? runWithInferenceBlockedFromSourceNode(editingArgument, () => getResolvedSignature(node, candidatesOutArray, checkMode)) :
+                getResolvedSignature(node, candidatesOutArray, checkMode);
             apparentArgumentCount = undefined;
             return res;
         }
@@ -22662,7 +22675,7 @@ namespace ts {
                 const properties = getPropertiesOfObjectType(target);
                 for (const targetProp of properties) {
                     const sourceProp = getPropertyOfType(source, targetProp.escapedName);
-                    if (sourceProp) {
+                    if (sourceProp && !some(sourceProp.declarations, hasSkipDirectInferenceFlag)) {
                         inferFromTypes(getTypeOfSymbol(sourceProp), getTypeOfSymbol(targetProp));
                     }
                 }
@@ -29760,7 +29773,7 @@ namespace ts {
 
             for (let i = 0; i < argCount; i++) {
                 const arg = args[i];
-                if (arg.kind !== SyntaxKind.OmittedExpression) {
+                if (arg.kind !== SyntaxKind.OmittedExpression && !(checkMode & CheckMode.IsForStringLiteralArgumentCompletions && hasSkipDirectInferenceFlag(arg))) {
                     const paramType = getTypeAtPosition(signature, i);
                     const argType = checkExpressionWithContextualType(arg, paramType, context, checkMode);
                     inferTypes(context.inferences, argType, paramType);
@@ -30500,7 +30513,7 @@ namespace ts {
                 }
             }
 
-            return getCandidateForOverloadFailure(node, candidates, args, !!candidatesOutArray);
+            return getCandidateForOverloadFailure(node, candidates, args, !!candidatesOutArray, checkMode);
 
             function addImplementationSuccessElaboration(failed: Signature, diagnostic: Diagnostic) {
                 const oldCandidatesForArgumentError = candidatesForArgumentError;
@@ -30614,6 +30627,7 @@ namespace ts {
             candidates: Signature[],
             args: readonly Expression[],
             hasCandidatesOutArray: boolean,
+            checkMode: CheckMode,
         ): Signature {
             Debug.assert(candidates.length > 0); // Else should not have called this.
             checkNodeDeferred(node);
@@ -30621,7 +30635,7 @@ namespace ts {
             // Don't do this if there is a `candidatesOutArray`,
             // because then we want the chosen best candidate to be one of the overloads, not a combination.
             return hasCandidatesOutArray || candidates.length === 1 || candidates.some(c => !!c.typeParameters)
-                ? pickLongestCandidateSignature(node, candidates, args)
+                ? pickLongestCandidateSignature(node, candidates, args, checkMode)
                 : createUnionOfSignaturesForOverloadFailure(candidates);
         }
 
@@ -30675,7 +30689,7 @@ namespace ts {
             return createSymbolWithType(first(sources), type);
         }
 
-        function pickLongestCandidateSignature(node: CallLikeExpression, candidates: Signature[], args: readonly Expression[]): Signature {
+        function pickLongestCandidateSignature(node: CallLikeExpression, candidates: Signature[], args: readonly Expression[], checkMode: CheckMode): Signature {
             // Pick the longest signature. This way we can get a contextual type for cases like:
             //     declare function f(a: { xa: number; xb: number; }, b: number);
             //     f({ |
@@ -30692,7 +30706,7 @@ namespace ts {
             const typeArgumentNodes: readonly TypeNode[] | undefined = callLikeExpressionMayHaveTypeArguments(node) ? node.typeArguments : undefined;
             const instantiated = typeArgumentNodes
                 ? createSignatureInstantiation(candidate, getTypeArgumentsFromNodes(typeArgumentNodes, typeParameters, isInJSFile(node)))
-                : inferSignatureInstantiationForOverloadFailure(node, typeParameters, candidate, args);
+                : inferSignatureInstantiationForOverloadFailure(node, typeParameters, candidate, args, checkMode);
             candidates[bestIndex] = instantiated;
             return instantiated;
         }
@@ -30708,9 +30722,9 @@ namespace ts {
             return typeArguments;
         }
 
-        function inferSignatureInstantiationForOverloadFailure(node: CallLikeExpression, typeParameters: readonly TypeParameter[], candidate: Signature, args: readonly Expression[]): Signature {
+        function inferSignatureInstantiationForOverloadFailure(node: CallLikeExpression, typeParameters: readonly TypeParameter[], candidate: Signature, args: readonly Expression[], checkMode: CheckMode): Signature {
             const inferenceContext = createInferenceContext(typeParameters, candidate, /*flags*/ isInJSFile(node) ? InferenceFlags.AnyDefault : InferenceFlags.None);
-            const typeArgumentTypes = inferTypeArguments(node, candidate, args, CheckMode.SkipContextSensitive | CheckMode.SkipGenericFunctions, inferenceContext);
+            const typeArgumentTypes = inferTypeArguments(node, candidate, args, checkMode | CheckMode.SkipContextSensitive | CheckMode.SkipGenericFunctions, inferenceContext);
             return createSignatureInstantiation(candidate, typeArgumentTypes);
         }
 
diff --git a/src/compiler/types.ts b/src/compiler/types.ts
index 3fee2e6eac415..5b83673b91415 100644
--- a/src/compiler/types.ts
+++ b/src/compiler/types.ts
@@ -4279,6 +4279,7 @@ namespace ts {
          */
         getResolvedSignature(node: CallLikeExpression, candidatesOutArray?: Signature[], argumentCount?: number): Signature | undefined;
         /* @internal */ getResolvedSignatureForSignatureHelp(node: CallLikeExpression, candidatesOutArray?: Signature[], argumentCount?: number): Signature | undefined;
+        /* @internal */ getResolvedSignatureForStringLiteralCompletions(call: CallLikeExpression, editingArgument: Node, candidatesOutArray: Signature[]): Signature | undefined;
         /* @internal */ getExpandedParameters(sig: Signature): readonly (readonly Symbol[])[];
         /* @internal */ hasEffectiveRestParameter(sig: Signature): boolean;
         /* @internal */ containsArgumentsReference(declaration: SignatureDeclaration): boolean;
diff --git a/src/harness/fourslashImpl.ts b/src/harness/fourslashImpl.ts
index b8513c3744297..24587abed5ce9 100644
--- a/src/harness/fourslashImpl.ts
+++ b/src/harness/fourslashImpl.ts
@@ -168,7 +168,7 @@ namespace FourSlash {
         // The position of the end of the current selection, or -1 if nothing is selected
         public selectionEnd = -1;
 
-        public lastKnownMarker = "";
+        public lastKnownMarker: string | undefined;
 
         // The file that's currently 'opened'
         public activeFile!: FourSlashFile;
@@ -400,7 +400,7 @@ namespace FourSlash {
                         continue;
                     }
                     const memo = Utils.memoize(
-                        (_version: number, _active: string, _caret: number, _selectEnd: number, _marker: string, ...args: any[]) => (ls[key] as Function)(...args),
+                        (_version: number, _active: string, _caret: number, _selectEnd: number, _marker: string | undefined, ...args: any[]) => (ls[key] as Function)(...args),
                         (...args) => args.map(a => a && typeof a === "object" ? JSON.stringify(a) : a).join("|,|")
                     );
                     proxy[key] = (...args: any[]) => memo(
@@ -540,8 +540,8 @@ namespace FourSlash {
         }
 
         private messageAtLastKnownMarker(message: string) {
-            const locationDescription = this.lastKnownMarker ? this.lastKnownMarker : this.getLineColStringAtPosition(this.currentCaretPosition);
-            return `At ${locationDescription}: ${message}`;
+            const locationDescription = this.lastKnownMarker !== undefined ? this.lastKnownMarker : this.getLineColStringAtPosition(this.currentCaretPosition);
+            return `At marker '${locationDescription}': ${message}`;
         }
 
         private assertionMessageAtLastKnownMarker(msg: string) {
@@ -864,7 +864,7 @@ namespace FourSlash {
             else {
                 for (const marker of toArray(options.marker)) {
                     this.goToMarker(marker);
-                    this.verifyCompletionsWorker(options);
+                    this.verifyCompletionsWorker({ ...options, marker });
                 }
             }
         }
diff --git a/src/services/stringCompletions.ts b/src/services/stringCompletions.ts
index ecf3fd73642ce..61f90c9817d7d 100644
--- a/src/services/stringCompletions.ts
+++ b/src/services/stringCompletions.ts
@@ -210,12 +210,13 @@ namespace ts.Completions.StringCompletions {
 
             case SyntaxKind.CallExpression:
             case SyntaxKind.NewExpression:
+            case SyntaxKind.JsxAttribute:
                 if (!isRequireCallArgument(node) && !isImportCall(parent)) {
-                    const argumentInfo = SignatureHelp.getArgumentInfoForCompletions(node, position, sourceFile);
+                    const argumentInfo = SignatureHelp.getArgumentInfoForCompletions(parent.kind === SyntaxKind.JsxAttribute ? parent.parent : node, position, sourceFile);
                     // Get string literal completions from specialized signatures of the target
                     // i.e. declare function f(a: 'A');
                     // f("/*completion position*/")
-                    return argumentInfo ? getStringLiteralCompletionsFromSignature(argumentInfo, typeChecker) : fromContextualType();
+                    return argumentInfo ? getStringLiteralCompletionsFromSignature(argumentInfo.invocation, node, argumentInfo, typeChecker) : fromContextualType();
                 }
                 // falls through (is `require("")` or `require(""` or `import("")`)
 
@@ -257,15 +258,21 @@ namespace ts.Completions.StringCompletions {
             type !== current && isLiteralTypeNode(type) && isStringLiteral(type.literal) ? type.literal.text : undefined);
     }
 
-    function getStringLiteralCompletionsFromSignature(argumentInfo: SignatureHelp.ArgumentInfoForCompletions, checker: TypeChecker): StringLiteralCompletionsFromTypes {
+    function getStringLiteralCompletionsFromSignature(call: CallLikeExpression, arg: StringLiteralLike, argumentInfo: SignatureHelp.ArgumentInfoForCompletions, checker: TypeChecker): StringLiteralCompletionsFromTypes {
         let isNewIdentifier = false;
-
         const uniques = new Map<string, true>();
         const candidates: Signature[] = [];
-        checker.getResolvedSignature(argumentInfo.invocation, candidates, argumentInfo.argumentCount);
+        const editingArgument = isJsxOpeningLikeElement(call) ? Debug.checkDefined(findAncestor(arg.parent, isJsxAttribute)) : arg;
+        checker.getResolvedSignatureForStringLiteralCompletions(call, editingArgument, candidates);
         const types = flatMap(candidates, candidate => {
             if (!signatureHasRestParameter(candidate) && argumentInfo.argumentCount > candidate.parameters.length) return;
-            const type = candidate.getTypeParameterAtPosition(argumentInfo.argumentIndex);
+            let type = candidate.getTypeParameterAtPosition(argumentInfo.argumentIndex);
+            if (isJsxOpeningLikeElement(call)) {
+                const propType = checker.getTypeOfPropertyOfType(type, (editingArgument as JsxAttribute).name.text);
+                if (propType) {
+                    type = propType;
+                }
+            }
             isNewIdentifier = isNewIdentifier || !!(type.flags & TypeFlags.String);
             return getStringLiteralTypes(type, uniques);
         });
diff --git a/tests/cases/fourslash/completionsLiteralOverload.ts b/tests/cases/fourslash/completionsLiteralOverload.ts
index 09486afe1ab62..c8d51a52bf63c 100644
--- a/tests/cases/fourslash/completionsLiteralOverload.ts
+++ b/tests/cases/fourslash/completionsLiteralOverload.ts
@@ -1,14 +1,23 @@
 /// <reference path="fourslash.ts" />
 
+// @allowJs: true
+
+// @Filename: /a.tsx
 //// interface Events {
+////   "": any;
 ////   drag: any;
 ////   dragenter: any;
 //// }
 //// declare function addListener<K extends keyof Events>(type: K, listener: (ev: Events[K]) => any): void;
-//// declare function addListener(type: string, listener: (ev: any) => any): void;
 ////
-//// addListener("/**/");
+//// declare function ListenerComponent<K extends keyof Events>(props: { type: K, onWhatever: (ev: Events[K]) => void }): JSX.Element;
+////
+//// addListener("/*ts*/");
+//// (<ListenerComponent type="/*tsx*/" />);
+
+// @Filename: /b.js
+//// addListener("/*js*/");
 
-verify.completions({ marker: "", isNewIdentifierLocation: true, exact: ["drag", "dragenter"] });
+verify.completions({ marker: ["ts", "tsx", "js"], exact: ["", "drag", "dragenter"] });
 edit.insert("drag");
-verify.completions({ isNewIdentifierLocation: true, exact: ["drag", "dragenter"] });
\ No newline at end of file
+verify.completions({ marker: ["ts", "tsx", "js"], exact: ["", "drag", "dragenter"] });
\ No newline at end of file