Skip to content

Commit

Permalink
Ugghhghgh
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewbranch committed Mar 24, 2022
1 parent 3b9bd27 commit cad98b9
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 66 deletions.
86 changes: 41 additions & 45 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -532,26 +532,13 @@ 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, /*stop*/ undefined, () => getContextualType(node, contextFlags));
}
return result;
return getContextualType(node, contextFlags);
},
getContextualTypeForCompletions: (nodeIn: Expression, editingNodeIn: Node) => {
return runWithInferenceBlockedFromSourceNode(editingNodeIn, editingNodeIn, () => getContextualType(nodeIn, ContextFlags.Completions));
},
getContextualTypeForObjectLiteralElement: nodeIn => {
const node = getParseTreeNode(nodeIn, isObjectLiteralElementLike);
Expand Down Expand Up @@ -741,33 +728,42 @@ namespace ts {
getMemberOverrideModifierStatus,
};

function runWithInferenceBlockedFromSourceNode<T>(node: Node | undefined, stop: Node | undefined, fn: () => T): T {
const containingCall = findAncestor(stop || node, isCallLikeExpression);
const containingCallResolvedSignature = containingCall && getNodeLinks(containingCall).resolvedSignature;
if (containingCall) {
let toMarkSkip = node!;
do {
getNodeLinks(toMarkSkip).skipDirectInference = true;
toMarkSkip = toMarkSkip.parent;
} while (toMarkSkip && toMarkSkip !== (stop || containingCall));
getNodeLinks(containingCall).resolvedSignature = undefined;
}
const result = fn();
if (containingCall) {
let toMarkSkip = node!;
do {
getNodeLinks(toMarkSkip).skipDirectInference = undefined;
toMarkSkip = toMarkSkip.parent;
} while (toMarkSkip && toMarkSkip !== (stop || containingCall));
getNodeLinks(containingCall).resolvedSignature = containingCallResolvedSignature;
}
return result;
}

function getResolvedSignatureWorker(nodeIn: CallLikeExpression, candidatesOutArray: Signature[] | undefined, argumentCount: number | undefined, checkMode: CheckMode, argumentIndex?: number): Signature | undefined {
let node = getParseTreeNode(nodeIn, isCallLikeExpression);
const node = getParseTreeNode(nodeIn, isCallLikeExpression);
let res;
apparentArgumentCount = argumentCount;
if (node && argumentIndex !== undefined) {
const replacementArg = setParentRecursive(factory.createAsExpression(factory.createStringLiteral(""), factory.createKeywordTypeNode(SyntaxKind.UnknownKeyword)), /*incremental*/ false);
switch (node.kind) {
case SyntaxKind.CallExpression:
node = factory.updateCallExpression(node, node.expression, node.typeArguments, [
...node.arguments.slice(0, argumentIndex),
replacementArg,
...node.arguments.slice(argumentIndex + 1),
]);
break;
case SyntaxKind.NewExpression:
node = factory.updateNewExpression(node, node.expression, node.typeArguments, [
...node.arguments?.slice(0, argumentIndex) || emptyArray,
replacementArg,
...node.arguments?.slice(argumentIndex + 1) || emptyArray,
]);
break;
default:
Debug.failBadSyntaxKind(node);
}
setParent(replacementArg, node);
setParent(node, nodeIn.parent);
const editingArgument =
tryCast(node, isCallOrNewExpression)?.arguments?.[argumentIndex] ||
tryCast(node, isJsxOpeningLikeElement)?.attributes?.properties[argumentIndex];
res = runWithInferenceBlockedFromSourceNode(editingArgument, /*stop*/ undefined, () => getResolvedSignature(node, candidatesOutArray, checkMode));
}
else {
res = node ? getResolvedSignature(node, candidatesOutArray, checkMode) : undefined;
}
apparentArgumentCount = argumentCount;
const res = node ? getResolvedSignature(node, candidatesOutArray, checkMode) : undefined;
apparentArgumentCount = undefined;
return res;
}
Expand Down Expand Up @@ -22687,7 +22683,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, d => !!getNodeLinks(d).skipDirectInference)) {
inferFromTypes(getTypeOfSymbol(sourceProp), getTypeOfSymbol(targetProp));
}
}
Expand Down Expand Up @@ -29785,7 +29781,7 @@ namespace ts {

for (let i = 0; i < argCount; i++) {
const arg = args[i];
if (arg.kind !== SyntaxKind.OmittedExpression) {
if (arg.kind !== SyntaxKind.OmittedExpression && !getNodeLinks(arg).skipDirectInference) {
const paramType = getTypeAtPosition(signature, i);
const argType = checkExpressionWithContextualType(arg, paramType, context, checkMode);
inferTypes(context.inferences, argType, paramType);
Expand Down
3 changes: 2 additions & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4269,6 +4269,7 @@ namespace ts {
/* @internal */ getContextualTypeForObjectLiteralElement(element: ObjectLiteralElementLike): Type | undefined;
/* @internal */ getContextualTypeForArgumentAtIndex(call: CallLikeExpression, argIndex: number): Type | undefined;
/* @internal */ getContextualTypeForJsxAttribute(attribute: JsxAttribute | JsxSpreadAttribute): Type | undefined;
/* @internal */ getContextualTypeForCompletions(node: Expression, editingNode: Node): Type | undefined;
/* @internal */ isContextSensitive(node: Expression | MethodDeclaration | ObjectLiteralElementLike | JsxAttributeLike): boolean;
/* @internal */ getTypeOfPropertyOfContextualType(type: Type, name: __String): Type | undefined;

Expand All @@ -4278,7 +4279,7 @@ namespace ts {
* @param argumentCount Apparent number of arguments, passed in case of a possibly incomplete call. This should come from an ArgumentListInfo. See `signatureHelp.ts`.
*/
getResolvedSignature(node: CallLikeExpression, candidatesOutArray?: Signature[], argumentCount?: number): Signature | undefined;
/* @internal */ getResolvedSignatureForStringLiteralCompletions(node: CallExpression | NewExpression, candidatesOutArray: Signature[], argumentCount: number, argumentIndex: number): Signature | undefined;
/* @internal */ getResolvedSignatureForStringLiteralCompletions(node: CallLikeExpression, candidatesOutArray: Signature[], argumentCount: number, argumentIndex: number): Signature | undefined;
/* @internal */ getResolvedSignatureForSignatureHelp(node: CallLikeExpression, candidatesOutArray?: Signature[], argumentCount?: number): Signature | undefined;
/* @internal */ getExpandedParameters(sig: Signature): readonly (readonly Symbol[])[];
/* @internal */ hasEffectiveRestParameter(sig: Signature): boolean;
Expand Down
10 changes: 5 additions & 5 deletions src/harness/fourslashImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -864,7 +864,7 @@ namespace FourSlash {
else {
for (const marker of toArray(options.marker)) {
this.goToMarker(marker);
this.verifyCompletionsWorker(options);
this.verifyCompletionsWorker({ ...options, marker });
}
}
}
Expand Down
21 changes: 14 additions & 7 deletions src/services/stringCompletions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(parent as CallExpression | NewExpression, argumentInfo, typeChecker) : fromContextualType();
return argumentInfo ? getStringLiteralCompletionsFromSignature(argumentInfo.invocation, node, argumentInfo, typeChecker) : fromContextualType();
}
// falls through (is `require("")` or `require(""` or `import("")`)

Expand All @@ -237,7 +238,7 @@ namespace ts.Completions.StringCompletions {
function fromContextualType(): StringLiteralCompletion {
// Get completion for string literal from string literal type
// i.e. var x: "hi" | "hello" = "/*completion position*/"
return { kind: StringLiteralCompletionKind.Types, types: getStringLiteralTypes(getContextualTypeFromParent(node, typeChecker)), isNewIdentifier: false };
return { kind: StringLiteralCompletionKind.Types, types: getStringLiteralTypes(getContextualTypeFromParent(node, typeChecker, ContextFlags.Completions)), isNewIdentifier: false };
}
}

Expand All @@ -257,15 +258,21 @@ namespace ts.Completions.StringCompletions {
type !== current && isLiteralTypeNode(type) && isStringLiteral(type.literal) ? type.literal.text : undefined);
}

function getStringLiteralCompletionsFromSignature(node: CallExpression | NewExpression, 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.getResolvedSignatureForStringLiteralCompletions(node, candidates, argumentInfo.argumentCount, argumentInfo.argumentIndex);
let argumentCount = argumentInfo.argumentCount;
let argumentIndex = argumentInfo.argumentIndex;
if (isJsxOpeningLikeElement(call)) {
argumentCount = call.attributes.properties.length;
argumentIndex = findIndex(call.attributes.properties, p => tryCast(p, isJsxAttribute)?.initializer === arg);
}
checker.getResolvedSignatureForStringLiteralCompletions(call, candidates, argumentCount, argumentIndex);
const types = flatMap(candidates, candidate => {
if (!signatureHasRestParameter(candidate) && argumentInfo.argumentCount > candidate.parameters.length) return;
const type = candidate.getTypeParameterAtPosition(argumentInfo.argumentIndex);
if (!signatureHasRestParameter(candidate) && argumentCount > candidate.parameters.length) return;
const type = candidate.getTypeParameterAtPosition(argumentIndex);
isNewIdentifier = isNewIdentifier || !!(type.flags & TypeFlags.String);
return getStringLiteralTypes(type, uniques);
});
Expand Down
8 changes: 4 additions & 4 deletions src/services/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2657,21 +2657,21 @@ namespace ts {
|| isAsExpression(expression) && isObjectLiteralExpression(expression.expression);
}

export function getContextualTypeFromParent(node: Expression, checker: TypeChecker): Type | undefined {
export function getContextualTypeFromParent(node: Expression, checker: TypeChecker, contextFlags?: ContextFlags): Type | undefined {
const { parent } = node;
switch (parent.kind) {
case SyntaxKind.NewExpression:
return checker.getContextualType(parent as NewExpression);
return checker.getContextualType(parent as NewExpression, contextFlags);
case SyntaxKind.BinaryExpression: {
const { left, operatorToken, right } = parent as BinaryExpression;
return isEqualityOperatorKind(operatorToken.kind)
? checker.getTypeAtLocation(node === right ? left : right)
: checker.getContextualType(node);
: checker.getContextualType(node, contextFlags);
}
case SyntaxKind.CaseClause:
return (parent as CaseClause).expression === node ? getSwitchedType(parent as CaseClause, checker) : undefined;
default:
return checker.getContextualType(node);
return checker.getContextualType(node, contextFlags);
}
}

Expand Down
6 changes: 2 additions & 4 deletions tests/cases/fourslash/completionsLiteralOverload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
//// 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;
////
//// declare function ListenerComponent<K extends keyof Events>(props: { type: K, onWhatever: (ev: Events[K]) => void }): JSX.Element;
////
Expand All @@ -19,7 +18,6 @@
// @Filename: /b.js
//// addListener("/*js*/");

verify.completions({ marker: ["ts", "js"], isNewIdentifierLocation: true, exact: ["", "drag", "dragenter"] });
verify.completions({ marker: "tsx", exact: ["", "drag", "dragenter"] });
verify.completions({ marker: ["ts", "tsx", "js"], exact: ["", "drag", "dragenter"] });
edit.insert("drag");
verify.completions({ isNewIdentifierLocation: true, exact: ["", "drag", "dragenter"] });
verify.completions({ marker: ["ts", "tsx", "js"], exact: ["", "drag", "dragenter"] });

0 comments on commit cad98b9

Please sign in to comment.