[WIP] Improve optional chaining checker performance (#33794)

* Improve optional chaining checker performance

* Improve optional chaining checker performance

* Add flags to Signature

* Inline getOptionalExpression

* split checks for optional chains

* Cache optional call signatures
This commit is contained in:
Ron Buckton
2019-10-18 17:27:47 -07:00
committed by GitHub
parent 91196fc53f
commit 556da72ffd
8 changed files with 210 additions and 152 deletions

View File

@@ -953,7 +953,7 @@ namespace ts {
}
if (expression.kind === SyntaxKind.TrueKeyword && flags & FlowFlags.FalseCondition ||
expression.kind === SyntaxKind.FalseKeyword && flags & FlowFlags.TrueCondition) {
if (!isOptionalChainRoot(expression.parent)) {
if (!isExpressionOfOptionalChainRoot(expression)) {
return unreachableFlow;
}
}

View File

@@ -327,10 +327,6 @@ namespace ts {
/** 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;
// This object is reused for `checkOptionalExpression` return values to avoid frequent GC due to nursery object allocations.
// This object represents a pool-size of 1.
const pooledOptionalTypeResult: { isOptional: boolean, type: Type } = { isOptional: false, type: undefined! };
// 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
@@ -712,10 +708,10 @@ namespace ts {
const noTypePredicate = createTypePredicate(TypePredicateKind.Identifier, "<<unresolved>>", 0, anyType);
const anySignature = createSignature(undefined, undefined, undefined, emptyArray, anyType, /*resolvedTypePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false);
const unknownSignature = createSignature(undefined, undefined, undefined, emptyArray, errorType, /*resolvedTypePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false);
const resolvingSignature = createSignature(undefined, undefined, undefined, emptyArray, anyType, /*resolvedTypePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false);
const silentNeverSignature = createSignature(undefined, undefined, undefined, emptyArray, silentNeverType, /*resolvedTypePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false);
const anySignature = createSignature(undefined, undefined, undefined, emptyArray, anyType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None);
const unknownSignature = createSignature(undefined, undefined, undefined, emptyArray, errorType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None);
const resolvingSignature = createSignature(undefined, undefined, undefined, emptyArray, anyType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None);
const silentNeverSignature = createSignature(undefined, undefined, undefined, emptyArray, silentNeverType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None);
const enumNumberIndexInfo = createIndexInfo(stringType, /*isReadonly*/ true);
@@ -7721,7 +7717,7 @@ namespace ts {
const signatures = getSignaturesOfType(type, SignatureKind.Construct);
if (signatures.length === 1) {
const s = signatures[0];
return !s.typeParameters && s.parameters.length === 1 && s.hasRestParameter && getElementTypeOfArrayType(getTypeOfParameter(s.parameters[0])) === anyType;
return !s.typeParameters && s.parameters.length === 1 && signatureHasRestParameter(s) && getElementTypeOfArrayType(getTypeOfParameter(s.parameters[0])) === anyType;
}
return false;
}
@@ -8612,10 +8608,9 @@ namespace ts {
resolvedReturnType: Type | undefined,
resolvedTypePredicate: TypePredicate | undefined,
minArgumentCount: number,
hasRestParameter: boolean,
hasLiteralTypes: boolean,
flags: SignatureFlags
): Signature {
const sig = new Signature(checker);
const sig = new Signature(checker, flags);
sig.declaration = declaration;
sig.typeParameters = typeParameters;
sig.parameters = parameters;
@@ -8623,8 +8618,6 @@ namespace ts {
sig.resolvedReturnType = resolvedReturnType;
sig.resolvedTypePredicate = resolvedTypePredicate;
sig.minArgumentCount = minArgumentCount;
sig.hasRestParameter = hasRestParameter;
sig.hasLiteralTypes = hasLiteralTypes;
sig.target = undefined;
sig.mapper = undefined;
return sig;
@@ -8632,7 +8625,7 @@ namespace ts {
function cloneSignature(sig: Signature): Signature {
const result = createSignature(sig.declaration, sig.typeParameters, sig.thisParameter, sig.parameters, /*resolvedReturnType*/ undefined,
/*resolvedTypePredicate*/ undefined, sig.minArgumentCount, sig.hasRestParameter, sig.hasLiteralTypes);
/*resolvedTypePredicate*/ undefined, sig.minArgumentCount, sig.flags & SignatureFlags.PropagatingFlags);
result.target = sig.target;
result.mapper = sig.mapper;
return result;
@@ -8646,14 +8639,19 @@ namespace ts {
return result;
}
function getOptionalCallSignature(signature: Signature) {
return signatureIsOptionalCall(signature) ? signature :
(signature.optionalCallSignatureCache || (signature.optionalCallSignatureCache = createOptionalCallSignature(signature)));
}
function createOptionalCallSignature(signature: Signature) {
const result = cloneSignature(signature);
result.isOptionalCall = true;
result.flags |= SignatureFlags.IsOptionalCall;
return result;
}
function getExpandedParameters(sig: Signature): readonly Symbol[] {
if (sig.hasRestParameter) {
if (signatureHasRestParameter(sig)) {
const restIndex = sig.parameters.length - 1;
const restParameter = sig.parameters[restIndex];
const restType = getTypeOfSymbol(restParameter);
@@ -8679,7 +8677,7 @@ namespace ts {
const baseConstructorType = getBaseConstructorTypeOfClass(classType);
const baseSignatures = getSignaturesOfType(baseConstructorType, SignatureKind.Construct);
if (baseSignatures.length === 0) {
return [createSignature(undefined, classType.localTypeParameters, undefined, emptyArray, classType, /*resolvedTypePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false)];
return [createSignature(undefined, classType.localTypeParameters, undefined, emptyArray, classType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None)];
}
const baseTypeNode = getBaseTypeNodeOfClass(classType)!;
const isJavaScript = isInJSFile(baseTypeNode);
@@ -8843,8 +8841,6 @@ namespace ts {
const params = combineUnionParameters(left, right);
const thisParam = combineUnionThisParam(left.thisParameter, right.thisParameter);
const minArgCount = Math.max(left.minArgumentCount, right.minArgumentCount);
const hasRestParam = left.hasRestParameter || right.hasRestParameter;
const hasLiteralTypes = left.hasLiteralTypes || right.hasLiteralTypes;
const result = createSignature(
declaration,
left.typeParameters || right.typeParameters,
@@ -8853,8 +8849,7 @@ namespace ts {
/*resolvedReturnType*/ undefined,
/*resolvedTypePredicate*/ undefined,
minArgCount,
hasRestParam,
hasLiteralTypes
(left.flags | right.flags) & SignatureFlags.PropagatingFlags
);
result.unionSignatures = concatenate(left.unionSignatures || [left], [right]);
return result;
@@ -9036,7 +9031,7 @@ namespace ts {
constructSignatures = addRange(constructSignatures.slice(), mapDefined(
type.callSignatures,
sig => isJSConstructor(sig.declaration) ?
createSignature(sig.declaration, sig.typeParameters, sig.thisParameter, sig.parameters, classType, /*resolvedTypePredicate*/ undefined, sig.minArgumentCount, sig.hasRestParameter, sig.hasLiteralTypes) :
createSignature(sig.declaration, sig.typeParameters, sig.thisParameter, sig.parameters, classType, /*resolvedTypePredicate*/ undefined, sig.minArgumentCount, sig.flags & SignatureFlags.PropagatingFlags) :
undefined));
}
if (!constructSignatures.length) {
@@ -10043,7 +10038,7 @@ namespace ts {
const links = getNodeLinks(declaration);
if (!links.resolvedSignature) {
const parameters: Symbol[] = [];
let hasLiteralTypes = false;
let flags = SignatureFlags.None;
let minArgumentCount = 0;
let thisParameter: Symbol | undefined;
let hasThisParameter = false;
@@ -10077,7 +10072,7 @@ namespace ts {
}
if (type && type.kind === SyntaxKind.LiteralType) {
hasLiteralTypes = true;
flags |= SignatureFlags.HasLiteralTypes;
}
// Record a new minimum argument count if this is not an optional parameter
@@ -10106,10 +10101,12 @@ namespace ts {
getDeclaredTypeOfClassOrInterface(getMergedSymbol((<ClassDeclaration>declaration.parent).symbol))
: undefined;
const typeParameters = classType ? classType.localTypeParameters : getTypeParametersFromDeclaration(declaration);
const hasRestLikeParameter = hasRestParameter(declaration) || isInJSFile(declaration) && maybeAddJsSyntheticRestParameter(declaration, parameters);
if (hasRestParameter(declaration) || isInJSFile(declaration) && maybeAddJsSyntheticRestParameter(declaration, parameters)) {
flags |= SignatureFlags.HasRestParameter;
}
links.resolvedSignature = createSignature(declaration, typeParameters, thisParameter, parameters,
/*resolvedReturnType*/ undefined, /*resolvedTypePredicate*/ undefined,
minArgumentCount, hasRestLikeParameter, hasLiteralTypes);
minArgumentCount, flags);
}
return links.resolvedSignature;
}
@@ -10264,8 +10261,8 @@ namespace ts {
signature.unionSignatures ? getUnionType(map(signature.unionSignatures, getReturnTypeOfSignature), UnionReduction.Subtype) :
getReturnTypeFromAnnotation(signature.declaration!) ||
(nodeIsMissing((<FunctionLikeDeclaration>signature.declaration).body) ? anyType : getReturnTypeFromBody(<FunctionLikeDeclaration>signature.declaration));
if (signature.isOptionalCall) {
type = propagateOptionalTypeMarker(type, /*wasOptional*/ true);
if (signatureIsOptionalCall(signature)) {
type = addOptionalTypeMarker(type);
}
if (!popTypeResolution()) {
if (signature.declaration) {
@@ -10325,7 +10322,7 @@ namespace ts {
}
function tryGetRestTypeOfSignature(signature: Signature): Type | undefined {
if (signature.hasRestParameter) {
if (signatureHasRestParameter(signature)) {
const sigRestType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]);
const restType = isTupleType(sigRestType) ? getRestTypeOfTupleType(sigRestType) : sigRestType;
return restType && getIndexTypeOfType(restType, IndexKind.Number);
@@ -12907,8 +12904,7 @@ namespace ts {
/*resolvedReturnType*/ undefined,
/*resolvedTypePredicate*/ undefined,
signature.minArgumentCount,
signature.hasRestParameter,
signature.hasLiteralTypes);
signature.flags & SignatureFlags.PropagatingFlags);
result.target = signature;
result.mapper = mapper;
return result;
@@ -13861,7 +13857,7 @@ namespace ts {
*/
function isAnySignature(s: Signature) {
return !s.typeParameters && (!s.thisParameter || isTypeAny(getTypeOfParameter(s.thisParameter))) && s.parameters.length === 1 &&
s.hasRestParameter && (getTypeOfParameter(s.parameters[0]) === anyArrayType || isTypeAny(getTypeOfParameter(s.parameters[0]))) &&
signatureHasRestParameter(s) && (getTypeOfParameter(s.parameters[0]) === anyArrayType || isTypeAny(getTypeOfParameter(s.parameters[0]))) &&
isTypeAny(getReturnTypeOfSignature(s));
}
@@ -16686,48 +16682,22 @@ namespace ts {
return strictNullChecks ? getUnionType([type, optionalType]) : type;
}
function isNotOptionalTypeMarker(type: Type) {
return type !== optionalType;
}
function removeOptionalTypeMarker(type: Type): Type {
return strictNullChecks ? filterType(type, t => t !== optionalType) : type;
return strictNullChecks ? filterType(type, isNotOptionalTypeMarker) : type;
}
function propagateOptionalTypeMarker(type: Type, wasOptional: boolean) {
return wasOptional ? addOptionalTypeMarker(type) : type;
}
function createPooledOptionalTypeResult(isOptional: boolean, type: Type) {
pooledOptionalTypeResult.isOptional = isOptional;
pooledOptionalTypeResult.type = type;
return pooledOptionalTypeResult;
}
function checkOptionalExpression(
parent: PropertyAccessExpression | QualifiedName | ElementAccessExpression | CallExpression,
expression: Expression | QualifiedName,
nullDiagnostic?: DiagnosticMessage,
undefinedDiagnostic?: DiagnosticMessage,
nullOrUndefinedDiagnostic?: DiagnosticMessage,
) {
let isOptional = false;
let type = checkExpression(expression);
if (isOptionalChain(parent)) {
if (parent.questionDotToken) {
// If we have a questionDotToken then we are an OptionalExpression and should remove `null` and
// `undefined` from the type and add the optionalType to the result, if needed.
isOptional = isNullableType(type);
return createPooledOptionalTypeResult(isOptional, isOptional ? getNonNullableType(type) : type);
}
// If we do not have a questionDotToken, then we are an OptionalChain and we remove the optionalType and
// indicate whether we need to add optionalType back into the result.
const nonOptionalType = removeOptionalTypeMarker(type);
if (nonOptionalType !== type) {
isOptional = true;
type = nonOptionalType;
}
}
type = checkNonNullType(type, expression, nullDiagnostic, undefinedDiagnostic, nullOrUndefinedDiagnostic);
return createPooledOptionalTypeResult(isOptional, type);
function getOptionalExpressionType(exprType: Type, expression: Expression) {
return isExpressionOfOptionalChainRoot(expression) ? getNonNullableType(exprType) :
isOptionalChain(expression) ? removeOptionalTypeMarker(exprType) :
exprType;
}
/**
@@ -18771,9 +18741,21 @@ namespace ts {
// expressions are potential type predicate function calls. In order to avoid triggering
// circularities in control flow analysis, we use getTypeOfDottedName when resolving the call
// target expression of an assertion.
const funcType = node.parent.kind === SyntaxKind.ExpressionStatement ? getTypeOfDottedName(node.expression, /*diagnostic*/ undefined) :
node.expression.kind !== SyntaxKind.SuperKeyword ? checkOptionalExpression(node, node.expression).type :
undefined;
let funcType: Type | undefined;
if (node.parent.kind === SyntaxKind.ExpressionStatement) {
funcType = getTypeOfDottedName(node.expression, /*diagnostic*/ undefined);
}
else if (node.expression.kind !== SyntaxKind.SuperKeyword) {
if (isOptionalChain(node)) {
funcType = checkNonNullType(
getOptionalExpressionType(checkExpression(node.expression), node.expression),
node.expression
);
}
else {
funcType = checkNonNullExpression(node.expression);
}
}
const signatures = getSignaturesOfType(funcType && getApparentType(funcType) || unknownType, SignatureKind.Call);
const candidate = signatures.length === 1 && !signatures[0].typeParameters ? signatures[0] :
some(signatures, hasTypePredicateOrNeverReturnType) ? getResolvedSignature(node) :
@@ -19780,7 +19762,7 @@ namespace ts {
// will be a subtype or the same type as the argument.
function narrowType(type: Type, expr: Expression, assumeTrue: boolean): Type {
// for `a?.b`, we emulate a synthetic `a !== null && a !== undefined` condition for `a`
if (isOptionalChainRoot(expr.parent) ||
if (isExpressionOfOptionalChainRoot(expr) ||
isBinaryExpression(expr.parent) && expr.parent.operatorToken.kind === SyntaxKind.QuestionQuestionToken && expr.parent.left === expr) {
return narrowTypeByOptionality(type, expr, assumeTrue);
}
@@ -22682,19 +22664,8 @@ namespace ts {
return !!forEachProperty(symbol, prop => !(prop.flags & SymbolFlags.Method));
}
function checkNonNullExpression(
node: Expression | QualifiedName,
nullDiagnostic?: DiagnosticMessage,
undefinedDiagnostic?: DiagnosticMessage,
nullOrUndefinedDiagnostic?: DiagnosticMessage,
) {
return checkNonNullType(
checkExpression(node),
node,
nullDiagnostic,
undefinedDiagnostic,
nullOrUndefinedDiagnostic
);
function checkNonNullExpression(node: Expression | QualifiedName) {
return checkNonNullType(checkExpression(node), node);
}
function isNullableType(type: Type) {
@@ -22705,12 +22676,26 @@ namespace ts {
return isNullableType(type) ? getNonNullableType(type) : type;
}
function checkNonNullType(
function reportObjectPossiblyNullOrUndefinedError(node: Node, flags: TypeFlags) {
error(node, flags & TypeFlags.Undefined ? flags & TypeFlags.Null ?
Diagnostics.Object_is_possibly_null_or_undefined :
Diagnostics.Object_is_possibly_undefined :
Diagnostics.Object_is_possibly_null
);
}
function reportCannotInvokePossiblyNullOrUndefinedError(node: Node, flags: TypeFlags) {
error(node, flags & TypeFlags.Undefined ? flags & TypeFlags.Null ?
Diagnostics.Cannot_invoke_an_object_which_is_possibly_null_or_undefined :
Diagnostics.Cannot_invoke_an_object_which_is_possibly_undefined :
Diagnostics.Cannot_invoke_an_object_which_is_possibly_null
);
}
function checkNonNullTypeWithReporter(
type: Type,
node: Node,
nullDiagnostic?: DiagnosticMessage,
undefinedDiagnostic?: DiagnosticMessage,
nullOrUndefinedDiagnostic?: DiagnosticMessage
reportError: (node: Node, kind: TypeFlags) => void
): Type {
if (strictNullChecks && type.flags & TypeFlags.Unknown) {
error(node, Diagnostics.Object_is_of_type_unknown);
@@ -22718,17 +22703,17 @@ namespace ts {
}
const kind = (strictNullChecks ? getFalsyFlags(type) : type.flags) & TypeFlags.Nullable;
if (kind) {
error(node, kind & TypeFlags.Undefined ? kind & TypeFlags.Null ?
(nullOrUndefinedDiagnostic || Diagnostics.Object_is_possibly_null_or_undefined) :
(undefinedDiagnostic || Diagnostics.Object_is_possibly_undefined) :
(nullDiagnostic || Diagnostics.Object_is_possibly_null)
);
reportError(node, kind);
const t = getNonNullableType(type);
return t.flags & (TypeFlags.Nullable | TypeFlags.Never) ? errorType : t;
}
return type;
}
function checkNonNullType(type: Type, node: Node) {
return checkNonNullTypeWithReporter(type, node, reportObjectPossiblyNullOrUndefinedError);
}
function checkNonNullNonVoidType(type: Type, node: Node): Type {
const nonNullType = checkNonNullType(type, node);
if (nonNullType !== errorType && nonNullType.flags & TypeFlags.Void) {
@@ -22738,11 +22723,18 @@ namespace ts {
}
function checkPropertyAccessExpression(node: PropertyAccessExpression) {
return checkPropertyAccessExpressionOrQualifiedName(node, node.expression, node.name);
return node.flags & NodeFlags.OptionalChain ? checkPropertyAccessChain(node as PropertyAccessChain) :
checkPropertyAccessExpressionOrQualifiedName(node, node.expression, checkNonNullExpression(node.expression), node.name);
}
function checkPropertyAccessChain(node: PropertyAccessChain) {
const leftType = checkExpression(node.expression);
const nonOptionalType = getOptionalExpressionType(leftType, node.expression);
return propagateOptionalTypeMarker(checkPropertyAccessExpressionOrQualifiedName(node, node.expression, checkNonNullType(nonOptionalType, node.expression), node.name), nonOptionalType !== leftType);
}
function checkQualifiedName(node: QualifiedName) {
return checkPropertyAccessExpressionOrQualifiedName(node, node.left, node.right);
return checkPropertyAccessExpressionOrQualifiedName(node, node.left, checkNonNullExpression(node.left), node.right);
}
function isMethodAccessForCall(node: Node) {
@@ -22752,8 +22744,7 @@ namespace ts {
return isCallOrNewExpression(node.parent) && node.parent.expression === node;
}
function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, right: Identifier) {
const { isOptional, type: leftType } = checkOptionalExpression(node, left);
function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, leftType: Type, right: Identifier) {
const parentSymbol = getNodeLinks(left).resolvedSymbol;
const assignmentKind = getAssignmentTargetKind(node);
const apparentType = getApparentType(assignmentKind !== AssignmentKind.None || isMethodAccessForCall(node) ? getWidenedType(leftType) : leftType);
@@ -22807,7 +22798,7 @@ namespace ts {
}
propType = getConstraintForLocation(getTypeOfSymbol(prop), node);
}
return propagateOptionalTypeMarker(getFlowTypeOfAccessExpression(node, prop, propType, right), isOptional);
return getFlowTypeOfAccessExpression(node, prop, propType, right);
}
function getFlowTypeOfAccessExpression(node: ElementAccessExpression | PropertyAccessExpression | QualifiedName, prop: Symbol | undefined, propType: Type, errorNode: Node) {
@@ -23164,7 +23155,17 @@ namespace ts {
}
function checkIndexedAccess(node: ElementAccessExpression): Type {
const { isOptional, type: exprType } = checkOptionalExpression(node, node.expression);
return node.flags & NodeFlags.OptionalChain ? checkElementAccessChain(node as ElementAccessChain) :
checkElementAccessExpression(node, checkNonNullExpression(node.expression));
}
function checkElementAccessChain(node: ElementAccessChain) {
const exprType = checkExpression(node.expression);
const nonOptionalType = getOptionalExpressionType(exprType, node.expression);
return propagateOptionalTypeMarker(checkElementAccessExpression(node, checkNonNullType(nonOptionalType, node.expression)), nonOptionalType !== exprType);
}
function checkElementAccessExpression(node: ElementAccessExpression, exprType: Type): Type {
const objectType = getAssignmentTargetKind(node) !== AssignmentKind.None || isMethodAccessForCall(node) ? getWidenedType(exprType) : exprType;
const indexExpression = node.argumentExpression;
const indexType = checkExpression(indexExpression);
@@ -23183,7 +23184,7 @@ namespace ts {
AccessFlags.Writing | (isGenericObjectType(objectType) && !isThisTypeParameter(objectType) ? AccessFlags.NoIndexSignatures : 0) :
AccessFlags.None;
const indexedAccessType = getIndexedAccessTypeOrUndefined(objectType, effectiveIndexType, node, accessFlags) || errorType;
return propagateOptionalTypeMarker(checkIndexedAccessIndexType(getFlowTypeOfAccessExpression(node, indexedAccessType.symbol, indexedAccessType, indexExpression), node), isOptional);
return checkIndexedAccessIndexType(getFlowTypeOfAccessExpression(node, indexedAccessType.symbol, indexedAccessType, indexExpression), node);
}
function checkThatExpressionIsProperSymbolReference(expression: Expression, expressionType: Type, reportError: boolean): boolean {
@@ -23296,7 +23297,7 @@ namespace ts {
// specialized signatures always need to be placed before non-specialized signatures regardless
// of the cutoff position; see GH#1133
if (signature.hasLiteralTypes) {
if (signatureHasLiteralTypes(signature)) {
specializedIndex++;
spliceIndex = specializedIndex;
// The cutoff index always needs to be greater than or equal to the specialized signature index
@@ -23308,7 +23309,7 @@ namespace ts {
spliceIndex = index;
}
result.splice(spliceIndex, 0, isOptionalCall ? createOptionalCallSignature(signature) : signature);
result.splice(spliceIndex, 0, isOptionalCall ? getOptionalCallSignature(signature) : signature);
}
}
@@ -24266,17 +24267,21 @@ namespace ts {
const { min: minArgumentCount, max: maxNonRestParam } = minAndMax(candidates, getNumNonRestParameters);
const parameters: Symbol[] = [];
for (let i = 0; i < maxNonRestParam; i++) {
const symbols = mapDefined(candidates, ({ parameters, hasRestParameter }) => hasRestParameter ?
i < parameters.length - 1 ? parameters[i] : last(parameters) :
i < parameters.length ? parameters[i] : undefined);
const symbols = mapDefined(candidates, s => signatureHasRestParameter(s) ?
i < s.parameters.length - 1 ? s.parameters[i] : last(s.parameters) :
i < s.parameters.length ? s.parameters[i] : undefined);
Debug.assert(symbols.length !== 0);
parameters.push(createCombinedSymbolFromTypes(symbols, mapDefined(candidates, candidate => tryGetTypeAtPosition(candidate, i))));
}
const restParameterSymbols = mapDefined(candidates, c => c.hasRestParameter ? last(c.parameters) : undefined);
const hasRestParameter = restParameterSymbols.length !== 0;
if (hasRestParameter) {
const restParameterSymbols = mapDefined(candidates, c => signatureHasRestParameter(c) ? last(c.parameters) : undefined);
let flags = SignatureFlags.None;
if (restParameterSymbols.length !== 0) {
const type = createArrayType(getUnionType(mapDefined(candidates, tryGetRestTypeOfSignature), UnionReduction.Subtype));
parameters.push(createCombinedSymbolForOverloadFailure(restParameterSymbols, type));
flags |= SignatureFlags.HasRestParameter;
}
if (candidates.some(signatureHasLiteralTypes)) {
flags |= SignatureFlags.HasLiteralTypes;
}
return createSignature(
candidates[0].declaration,
@@ -24286,13 +24291,12 @@ namespace ts {
/*resolvedReturnType*/ getIntersectionType(candidates.map(getReturnTypeOfSignature)),
/*typePredicate*/ undefined,
minArgumentCount,
hasRestParameter,
/*hasLiteralTypes*/ candidates.some(c => c.hasLiteralTypes));
flags);
}
function getNumNonRestParameters(signature: Signature): number {
const numParams = signature.parameters.length;
return signature.hasRestParameter ? numParams - 1 : numParams;
return signatureHasRestParameter(signature) ? numParams - 1 : numParams;
}
function createCombinedSymbolFromTypes(sources: readonly Symbol[], types: Type[]): Symbol {
@@ -24383,12 +24387,20 @@ namespace ts {
return resolveUntypedCall(node);
}
const { isOptional, type: funcType } = checkOptionalExpression(
node,
let isOptional: boolean;
let funcType = checkExpression(node.expression);
if (isCallChain(node)) {
const nonOptionalType = getOptionalExpressionType(funcType, node.expression);
isOptional = nonOptionalType !== funcType;
funcType = nonOptionalType;
}
else {
isOptional = false;
}
funcType = checkNonNullTypeWithReporter(
funcType,
node.expression,
Diagnostics.Cannot_invoke_an_object_which_is_possibly_null,
Diagnostics.Cannot_invoke_an_object_which_is_possibly_undefined,
Diagnostics.Cannot_invoke_an_object_which_is_possibly_null_or_undefined
reportCannotInvokePossiblyNullOrUndefinedError
);
if (funcType === silentNeverType) {
@@ -24842,8 +24854,7 @@ namespace ts {
typeSymbol ? getDeclaredTypeOfSymbol(typeSymbol) : errorType,
/*returnTypePredicate*/ undefined,
1,
/*hasRestparameter*/ false,
/*hasLiteralTypes*/ false
SignatureFlags.None
);
}
@@ -24882,7 +24893,7 @@ namespace ts {
function isPotentiallyUncalledDecorator(decorator: Decorator, signatures: readonly Signature[]) {
return signatures.length && every(signatures, signature =>
signature.minArgumentCount === 0 &&
!signature.hasRestParameter &&
!signatureHasRestParameter(signature) &&
signature.parameters.length < getDecoratorArgumentCount(decorator, signature));
}
@@ -25298,7 +25309,7 @@ namespace ts {
}
function getParameterNameAtPosition(signature: Signature, pos: number) {
const paramCount = signature.parameters.length - (signature.hasRestParameter ? 1 : 0);
const paramCount = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0);
if (pos < paramCount) {
return signature.parameters[pos].escapedName;
}
@@ -25317,11 +25328,11 @@ namespace ts {
}
function tryGetTypeAtPosition(signature: Signature, pos: number): Type | undefined {
const paramCount = signature.parameters.length - (signature.hasRestParameter ? 1 : 0);
const paramCount = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0);
if (pos < paramCount) {
return getTypeOfParameter(signature.parameters[pos]);
}
if (signature.hasRestParameter) {
if (signatureHasRestParameter(signature)) {
// We want to return the value undefined for an out of bounds parameter position,
// so we need to check bounds here before calling getIndexedAccessType (which
// otherwise would return the type 'undefined').
@@ -25358,7 +25369,7 @@ namespace ts {
function getParameterCount(signature: Signature) {
const length = signature.parameters.length;
if (signature.hasRestParameter) {
if (signatureHasRestParameter(signature)) {
const restType = getTypeOfSymbol(signature.parameters[length - 1]);
if (isTupleType(restType)) {
return length + getTypeArguments(restType).length - 1;
@@ -25368,7 +25379,7 @@ namespace ts {
}
function getMinArgumentCount(signature: Signature) {
if (signature.hasRestParameter) {
if (signatureHasRestParameter(signature)) {
const restType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]);
if (isTupleType(restType)) {
const minLength = restType.target.minLength;
@@ -25381,7 +25392,7 @@ namespace ts {
}
function hasEffectiveRestParameter(signature: Signature) {
if (signature.hasRestParameter) {
if (signatureHasRestParameter(signature)) {
const restType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]);
return !isTupleType(restType) || restType.target.hasRestElement;
}
@@ -25389,7 +25400,7 @@ namespace ts {
}
function getEffectiveRestType(signature: Signature) {
if (signature.hasRestParameter) {
if (signatureHasRestParameter(signature)) {
const restType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]);
return isTupleType(restType) ? getRestArrayTypeOfTupleType(restType) : restType;
}
@@ -25410,7 +25421,7 @@ namespace ts {
}
function inferFromAnnotatedParameters(signature: Signature, context: Signature, inferenceContext: InferenceContext) {
const len = signature.parameters.length - (signature.hasRestParameter ? 1 : 0);
const len = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0);
for (let i = 0; i < len; i++) {
const declaration = <ParameterDeclaration>signature.parameters[i].valueDeclaration;
if (declaration.type) {
@@ -25444,7 +25455,7 @@ namespace ts {
assignTypeToParameterAndFixTypeParameters(signature.thisParameter!, getTypeOfSymbol(context.thisParameter));
}
}
const len = signature.parameters.length - (signature.hasRestParameter ? 1 : 0);
const len = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0);
for (let i = 0; i < len; i++) {
const parameter = signature.parameters[i];
if (!getEffectiveTypeAnnotationNode(<ParameterDeclaration>parameter.valueDeclaration)) {
@@ -25452,7 +25463,7 @@ namespace ts {
assignTypeToParameterAndFixTypeParameters(parameter, contextualParameterType);
}
}
if (signature.hasRestParameter) {
if (signatureHasRestParameter(signature)) {
// parameter might be a transient symbol generated by use of `arguments` in the function body.
const parameter = last(signature.parameters);
if (isTransientSymbol(parameter) || !getEffectiveTypeAnnotationNode(<ParameterDeclaration>parameter.valueDeclaration)) {
@@ -25890,7 +25901,7 @@ namespace ts {
return links.contextFreeType;
}
const returnType = getReturnTypeFromBody(node, checkMode);
const returnOnlySignature = createSignature(undefined, undefined, undefined, emptyArray, returnType, /*resolvedTypePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false);
const returnOnlySignature = createSignature(undefined, undefined, undefined, emptyArray, returnType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None);
const returnOnlyType = createAnonymousType(node.symbol, emptySymbols, [returnOnlySignature], emptyArray, undefined, undefined);
returnOnlyType.objectFlags |= ObjectFlags.NonInferrableType;
return links.contextFreeType = returnOnlyType;
@@ -27355,7 +27366,18 @@ namespace ts {
// Optimize for the common case of a call to a function with a single non-generic call
// signature where we can just fetch the return type without checking the arguments.
if (isCallExpression(expr) && expr.expression.kind !== SyntaxKind.SuperKeyword && !isRequireCall(expr, /*checkArgumentIsStringLiteralLike*/ true) && !isSymbolOrSymbolForCall(expr)) {
const { isOptional, type: funcType } = checkOptionalExpression(expr, expr.expression);
let isOptional: boolean;
let funcType: Type;
if (isCallChain(expr)) {
funcType = checkExpression(expr.expression);
const nonOptionalType = getOptionalExpressionType(funcType, expr.expression);
isOptional = funcType !== nonOptionalType;
funcType = checkNonNullType(nonOptionalType, expr.expression);
}
else {
isOptional = false;
funcType = checkNonNullExpression(expr.expression);
}
const signature = getSingleCallSignature(funcType);
if (signature && !signature.typeParameters) {
return propagateOptionalTypeMarker(getReturnTypeOfSignature(signature), isOptional);
@@ -27630,7 +27652,7 @@ namespace ts {
}
else {
if (typePredicate.parameterIndex >= 0) {
if (signature.hasRestParameter && typePredicate.parameterIndex === signature.parameters.length - 1) {
if (signatureHasRestParameter(signature) && typePredicate.parameterIndex === signature.parameters.length - 1) {
error(parameterName, Diagnostics.A_type_predicate_cannot_reference_a_rest_parameter);
}
else {
@@ -36062,4 +36084,16 @@ namespace ts {
case IterationTypeKind.Next: return "nextType";
}
}
export function signatureHasRestParameter(s: Signature) {
return !!(s.flags & SignatureFlags.HasRestParameter);
}
export function signatureHasLiteralTypes(s: Signature) {
return !!(s.flags & SignatureFlags.HasLiteralTypes);
}
export function signatureIsOptionalCall(s: Signature) {
return !!(s.flags & SignatureFlags.IsOptionalCall);
}
}

View File

@@ -3451,8 +3451,7 @@ namespace ts {
resolvedReturnType: Type,
typePredicate: TypePredicate | undefined,
minArgumentCount: number,
hasRestParameter: boolean,
hasLiteralTypes: boolean,
flags: SignatureFlags
): Signature;
/* @internal */ createSymbol(flags: SymbolFlags, name: __String): TransientSymbol;
/* @internal */ createIndexInfo(type: Type, isReadonly: boolean, declaration?: SignatureDeclaration): IndexInfo;
@@ -4671,7 +4670,22 @@ namespace ts {
Construct,
}
/* @internal */
export const enum SignatureFlags {
None = 0,
HasRestParameter = 1 << 0, // Indicates last parameter is rest parameter
HasLiteralTypes = 1 << 1, // Indicates signature is specialized
IsOptionalCall = 1 << 2, // Indicates signature comes from a CallChain
// We do not propagate `IsOptionalCall` to instantiated signatures, as that would result in us
// attempting to add `| undefined` on each recursive call to `getReturnTypeOfSignature` when
// instantiating the return type.
PropagatingFlags = HasRestParameter | HasLiteralTypes,
}
export interface Signature {
/* @internal */ flags: SignatureFlags;
/* @internal */ checker?: TypeChecker;
declaration?: SignatureDeclaration | JSDocSignature; // Originating declaration
typeParameters?: readonly TypeParameter[]; // Type parameters (undefined if non-generic)
parameters: readonly Symbol[]; // Parameters
@@ -4688,10 +4702,6 @@ namespace ts {
/* @internal */
minArgumentCount: number; // Number of non-optional parameters
/* @internal */
hasRestParameter: boolean; // True if last parameter is rest parameter
/* @internal */
hasLiteralTypes: boolean; // True if specialized
/* @internal */
target?: Signature; // Instantiation target
/* @internal */
mapper?: TypeMapper; // Instantiation mapper
@@ -4702,11 +4712,11 @@ namespace ts {
/* @internal */
canonicalSignatureCache?: Signature; // Canonical version of signature (deferred)
/* @internal */
optionalCallSignatureCache?: Signature; // Optional chained call version of signature (deferred)
/* @internal */
isolatedSignatureType?: ObjectType; // A manufactured type that just contains the signature for purposes of signature comparison
/* @internal */
instantiations?: Map<Signature>; // Generic signature instantiation cache
/* @internal */
isOptionalCall?: boolean;
}
export const enum IndexKind {

View File

@@ -5911,6 +5911,14 @@ namespace ts {
|| kind === SyntaxKind.CallExpression);
}
/**
* Determines whether a node is the expression preceding an optional chain (i.e. `a` in `a?.b`).
*/
/* @internal */
export function isExpressionOfOptionalChainRoot(node: Node): node is Expression & { parent: OptionalChainRoot } {
return isOptionalChainRoot(node.parent) && node.parent.expression === node;
}
export function isNewExpression(node: Node): node is NewExpression {
return node.kind === SyntaxKind.NewExpression;
}
@@ -7310,7 +7318,7 @@ namespace ts {
getSourceFileConstructor(): new (kind: SyntaxKind.SourceFile, pos?: number, end?: number) => SourceFile;
getSymbolConstructor(): new (flags: SymbolFlags, name: __String) => Symbol;
getTypeConstructor(): new (checker: TypeChecker, flags: TypeFlags) => Type;
getSignatureConstructor(): new (checker: TypeChecker) => Signature;
getSignatureConstructor(): new (checker: TypeChecker, flags: SignatureFlags) => Signature;
getSourceMapSourceConstructor(): new (fileName: string, text: string, skipTrivia?: (pos: number) => number) => SourceMapSource;
}
@@ -7331,7 +7339,12 @@ namespace ts {
}
}
function Signature() {}
function Signature(this: Signature, checker: TypeChecker, flags: SignatureFlags) {
this.flags = flags;
if (Debug.isDebugging) {
this.checker = checker;
}
}
function Node(this: Node, kind: SyntaxKind, pos: number, end: number) {
this.pos = pos;

View File

@@ -233,14 +233,14 @@ namespace ts.codefix {
let someSigHasRestParameter = false;
for (const sig of signatures) {
minArgumentCount = Math.min(sig.minArgumentCount, minArgumentCount);
if (sig.hasRestParameter) {
if (signatureHasRestParameter(sig)) {
someSigHasRestParameter = true;
}
if (sig.parameters.length >= maxArgsSignature.parameters.length && (!sig.hasRestParameter || maxArgsSignature.hasRestParameter)) {
if (sig.parameters.length >= maxArgsSignature.parameters.length && (!signatureHasRestParameter(sig) || signatureHasRestParameter(maxArgsSignature))) {
maxArgsSignature = sig;
}
}
const maxNonRestArgs = maxArgsSignature.parameters.length - (maxArgsSignature.hasRestParameter ? 1 : 0);
const maxNonRestArgs = maxArgsSignature.parameters.length - (signatureHasRestParameter(maxArgsSignature) ? 1 : 0);
const maxArgsParameterSymbolNames = maxArgsSignature.parameters.map(symbol => symbol.name);
const parameters = createDummyParameters(maxNonRestArgs, maxArgsParameterSymbolNames, /* types */ undefined, minArgumentCount, /*inJs*/ false);

View File

@@ -1112,7 +1112,7 @@ namespace ts.codefix {
}
const returnType = combineFromUsage(combineUsages(calls.map(call => call.return_)));
// TODO: GH#18217
return checker.createSignature(/*declaration*/ undefined!, /*typeParameters*/ undefined, /*thisParameter*/ undefined, parameters, returnType, /*typePredicate*/ undefined, length, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false);
return checker.createSignature(/*declaration*/ undefined!, /*typeParameters*/ undefined, /*thisParameter*/ undefined, parameters, returnType, /*typePredicate*/ undefined, length, SignatureFlags.None);
}
function addCandidateType(usage: Usage, type: Type | undefined) {

View File

@@ -464,6 +464,7 @@ namespace ts {
}
class SignatureObject implements Signature {
flags: SignatureFlags;
checker: TypeChecker;
declaration!: SignatureDeclaration;
typeParameters?: TypeParameter[];
@@ -473,8 +474,6 @@ namespace ts {
resolvedTypePredicate: TypePredicate | undefined;
minTypeArgumentCount!: number;
minArgumentCount!: number;
hasRestParameter!: boolean;
hasLiteralTypes!: boolean;
// Undefined is used to indicate the value has not been computed. If, after computing, the
// symbol has no doc comment, then the empty array will be returned.
@@ -484,9 +483,11 @@ namespace ts {
// symbol has no doc comment, then the empty array will be returned.
jsDocTags?: JSDocTagInfo[];
constructor(checker: TypeChecker) {
constructor(checker: TypeChecker, flags: SignatureFlags) {
this.checker = checker;
this.flags = flags;
}
getDeclaration(): SignatureDeclaration {
return this.declaration;
}

View File

@@ -204,7 +204,7 @@ namespace ts.Completions.StringCompletions {
const candidates: Signature[] = [];
checker.getResolvedSignature(argumentInfo.invocation, candidates, argumentInfo.argumentCount);
const types = flatMap(candidates, candidate => {
if (!candidate.hasRestParameter && argumentInfo.argumentCount > candidate.parameters.length) return;
if (!signatureHasRestParameter(candidate) && argumentInfo.argumentCount > candidate.parameters.length) return;
const type = checker.getParameterType(candidate, argumentInfo.argumentIndex);
isNewIdentifier = isNewIdentifier || !!(type.flags & TypeFlags.String);
return getStringLiteralTypes(type, uniques);