Skip to content

Provide better services for incomplete generic calls #16535

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
4 commits merged into from
Jun 27, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 71 additions & 33 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ namespace ts {
undefinedSymbol.declarations = [];
const argumentsSymbol = createSymbol(SymbolFlags.Property, "arguments");

/** This will be set during calls to `getResolvedSignature` where services determines an apparent number of arguments greater than what is actually provided. */
let apparentArgumentCount: number | undefined;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice if we just computed this at the moment we need it, but that seems to involve getting the tokens array, which is currently code exclusive to services. See getArgumentCount in signatureHelp.ts.


// for public members that accept a Node or one of its subtypes, we must guard against
// synthetic nodes created during transformations by calling `getParseTreeNode`.
// for most of these, we perform the guard only on `checker` to avoid any possible
Expand Down Expand Up @@ -154,9 +157,12 @@ namespace ts {
return node ? getContextualType(node) : undefined;
},
getFullyQualifiedName,
getResolvedSignature: (node, candidatesOutArray?) => {
getResolvedSignature: (node, candidatesOutArray, theArgumentCount) => {
node = getParseTreeNode(node, isCallLikeExpression);
return node ? getResolvedSignature(node, candidatesOutArray) : undefined;
apparentArgumentCount = theArgumentCount;
const res = node ? getResolvedSignature(node, candidatesOutArray) : undefined;
apparentArgumentCount = undefined;
return res;
},
getConstantValue: node => {
node = getParseTreeNode(node, canHaveConstantValue);
Expand Down Expand Up @@ -6306,12 +6312,12 @@ namespace ts {
// If a type parameter does not have a default type, or if the default type
// is a forward reference, the empty object type is used.
for (let i = numTypeArguments; i < numTypeParameters; i++) {
typeArguments[i] = isJavaScript ? anyType : emptyObjectType;
typeArguments[i] = getDefaultTypeArgumentType(isJavaScript);
}
for (let i = numTypeArguments; i < numTypeParameters; i++) {
const mapper = createTypeMapper(typeParameters, typeArguments);
const defaultType = getDefaultFromTypeParameter(typeParameters[i]);
typeArguments[i] = defaultType ? instantiateType(defaultType, mapper) : isJavaScript ? anyType : emptyObjectType;
typeArguments[i] = defaultType ? instantiateType(defaultType, mapper) : getDefaultTypeArgumentType(isJavaScript);
}
}
}
Expand Down Expand Up @@ -7961,15 +7967,16 @@ namespace ts {
};
}

function createTypeMapper(sources: Type[], targets: Type[]): TypeMapper {
function createTypeMapper(sources: TypeParameter[], targets: Type[]): TypeMapper {
Debug.assert(targets === undefined || sources.length === targets.length);
const mapper: TypeMapper = sources.length === 1 ? makeUnaryTypeMapper(sources[0], targets ? targets[0] : anyType) :
sources.length === 2 ? makeBinaryTypeMapper(sources[0], targets ? targets[0] : anyType, sources[1], targets ? targets[1] : anyType) :
makeArrayTypeMapper(sources, targets);
mapper.mappedTypes = sources;
return mapper;
}

function createTypeEraser(sources: Type[]): TypeMapper {
function createTypeEraser(sources: TypeParameter[]): TypeMapper {
return createTypeMapper(sources, /*targets*/ undefined);
}

Expand Down Expand Up @@ -10619,7 +10626,7 @@ namespace ts {
context));
}
else {
inferredType = context.flags & InferenceFlags.AnyDefault ? anyType : emptyObjectType;
inferredType = getDefaultTypeArgumentType(!!(context.flags & InferenceFlags.AnyDefault));
}
}
inference.inferredType = inferredType;
Expand All @@ -10635,6 +10642,10 @@ namespace ts {
return inferredType;
}

function getDefaultTypeArgumentType(isInJavaScriptFile: boolean): Type {
return isInJavaScriptFile ? anyType : emptyObjectType;
}

function getInferredTypes(context: InferenceContext): Type[] {
const result: Type[] = [];
for (let i = 0; i < context.inferences.length; i++) {
Expand Down Expand Up @@ -12784,7 +12795,9 @@ namespace ts {
const args = getEffectiveCallArguments(callTarget);
const argIndex = indexOf(args, arg);
if (argIndex >= 0) {
const signature = getResolvedOrAnySignature(callTarget);
// 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.
const signature = getNodeLinks(callTarget).resolvedSignature === resolvingSignature ? resolvingSignature : getResolvedSignature(callTarget);
return getTypeAtPosition(signature, argIndex);
}
return undefined;
Expand Down Expand Up @@ -14948,16 +14961,14 @@ namespace ts {
}

function inferTypeArguments(node: CallLikeExpression, signature: Signature, args: Expression[], excludeArgument: boolean[], context: InferenceContext): Type[] {
const inferences = context.inferences;

// Clear out all the inference results from the last time inferTypeArguments was called on this context
for (let i = 0; i < inferences.length; i++) {
for (const inference of context.inferences) {
// As an optimization, we don't have to clear (and later recompute) inferred types
// for type parameters that have already been fixed on the previous call to inferTypeArguments.
// It would be just as correct to reset all of them. But then we'd be repeating the same work
// for the type parameters that were fixed, namely the work done by getInferredType.
if (!inferences[i].isFixed) {
inferences[i].inferredType = undefined;
if (!inference.isFixed) {
inference.inferredType = undefined;
}
}

Expand Down Expand Up @@ -15626,27 +15637,43 @@ namespace ts {
}

// No signature was applicable. We have already reported the errors for the invalid signature.
// If this is a type resolution session, e.g. Language Service, try to get better information that anySignature.
// Pick the first candidate that matches the arity. This way we can get a contextual type for cases like:
// declare function f(a: { xa: number; xb: number; });
// f({ |
// If this is a type resolution session, e.g. Language Service, try to get better information than anySignature.
// 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({ |
// Also, use explicitly-supplied type arguments if they are provided, so we can get a contextual signature in cases like:
// declare function f<T>(k: keyof T);
// f<Foo>("
if (!produceDiagnostics) {
for (let candidate of candidates) {
if (hasCorrectArity(node, args, candidate)) {
if (candidate.typeParameters && typeArguments) {
candidate = getSignatureInstantiation(candidate, map(typeArguments, getTypeFromTypeNode));
}
return candidate;
Debug.assert(candidates.length > 0); // Else would have exited above.
const bestIndex = getLongestCandidateIndex(candidates, apparentArgumentCount === undefined ? args.length : apparentArgumentCount);
const candidate = candidates[bestIndex];

const { typeParameters } = candidate;
if (typeParameters && callLikeExpressionMayHaveTypeArguments(node) && node.typeArguments) {
const typeArguments = node.typeArguments.map(getTypeOfNode);
while (typeArguments.length > typeParameters.length) {
typeArguments.pop();
}
while (typeArguments.length < typeParameters.length) {
typeArguments.push(getDefaultTypeArgumentType(isInJavaScriptFile(node)));
}

const instantiated = createSignatureInstantiation(candidate, typeArguments);
candidates[bestIndex] = instantiated;
return instantiated;
}

return candidate;
}

return resolveErrorCall(node);

function chooseOverload(candidates: Signature[], relation: Map<RelationComparisonResult>, signatureHelpTrailingComma = false) {
candidateForArgumentError = undefined;
candidateForTypeArgumentError = undefined;
for (const originalCandidate of candidates) {
for (let candidateIndex = 0; candidateIndex < candidates.length; candidateIndex++) {
const originalCandidate = candidates[candidateIndex];
if (!hasCorrectArity(node, args, originalCandidate, signatureHelpTrailingComma)) {
continue;
}
Expand Down Expand Up @@ -15678,6 +15705,7 @@ namespace ts {
}
const index = excludeArgument ? indexOf(excludeArgument, /*value*/ true) : -1;
if (index < 0) {
candidates[candidateIndex] = candidate;
return candidate;
}
excludeArgument[index] = false;
Expand All @@ -15689,6 +15717,24 @@ namespace ts {

}

function getLongestCandidateIndex(candidates: Signature[], argsCount: number): number {
let maxParamsIndex = -1;
let maxParams = -1;

for (let i = 0; i < candidates.length; i++) {
const candidate = candidates[i];
if (candidate.hasRestParameter || candidate.parameters.length >= argsCount) {
return i;
}
if (candidate.parameters.length > maxParams) {
maxParams = candidate.parameters.length;
maxParamsIndex = i;
}
}

return maxParamsIndex;
}

function resolveCallExpression(node: CallExpression, candidatesOutArray: Signature[]): Signature {
if (node.expression.kind === SyntaxKind.SuperKeyword) {
const superType = checkSuperExpression(node.expression);
Expand Down Expand Up @@ -16003,9 +16049,7 @@ namespace ts {

const callSignatures = elementType && getSignaturesOfType(elementType, SignatureKind.Call);
if (callSignatures && callSignatures.length > 0) {
let callSignature: Signature;
callSignature = resolveCall(openingLikeElement, callSignatures, candidatesOutArray);
return callSignature;
return resolveCall(openingLikeElement, callSignatures, candidatesOutArray);
}

return undefined;
Expand Down Expand Up @@ -16055,12 +16099,6 @@ namespace ts {
return result;
}

function getResolvedOrAnySignature(node: CallLikeExpression) {
// 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.
return getNodeLinks(node).resolvedSignature === resolvingSignature ? resolvingSignature : getResolvedSignature(node);
}

/**
* Indicates whether a declaration can be treated as a constructor in a JavaScript
* file.
Expand Down
10 changes: 7 additions & 3 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2582,7 +2582,11 @@ namespace ts {
getAugmentedPropertiesOfType(type: Type): Symbol[];
getRootSymbols(symbol: Symbol): Symbol[];
getContextualType(node: Expression): Type | undefined;
getResolvedSignature(node: CallLikeExpression, candidatesOutArray?: Signature[]): Signature | undefined;
/**
* returns unknownSignature in the case of an error. Don't know when it returns undefined.
* @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;
getSignatureFromDeclaration(declaration: SignatureDeclaration): Signature | undefined;
isImplementationOfOverload(node: FunctionLikeDeclaration): boolean | undefined;
isUndefinedSymbol(symbol: Symbol): boolean;
Expand Down Expand Up @@ -3377,8 +3381,8 @@ namespace ts {
/* @internal */
export interface TypeMapper {
(t: TypeParameter): Type;
mappedTypes?: Type[]; // Types mapped by this mapper
instantiations?: Type[]; // Cache of instantiations created using this type mapper.
mappedTypes?: TypeParameter[]; // Types mapped by this mapper
instantiations?: Type[]; // Cache of instantiations created using this type mapper.
}

export const enum InferencePriority {
Expand Down
5 changes: 5 additions & 0 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4735,6 +4735,11 @@ namespace ts {
// All node tests in the following list should *not* reference parent pointers so that
// they may be used with transformations.
namespace ts {
/* @internal */
export function isSyntaxList(n: Node): n is SyntaxList {
return n.kind === SyntaxKind.SyntaxList;
}

/* @internal */
export function isNode(node: Node) {
return isNodeKind(node.kind);
Expand Down
2 changes: 1 addition & 1 deletion src/services/completions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ namespace ts.Completions {
const entries: CompletionEntry[] = [];
const uniques = createMap<true>();

typeChecker.getResolvedSignature(argumentInfo.invocation, candidates);
typeChecker.getResolvedSignature(argumentInfo.invocation, candidates, argumentInfo.argumentCount);

for (const candidate of candidates) {
addStringLiteralCompletionsFromType(typeChecker.getParameterType(candidate, argumentInfo.argumentIndex), entries, typeChecker, uniques);
Expand Down
Loading