diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 6bd6045a55101..085eab04a686a 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -948,7 +948,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; } } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6ece23f022ab3..d210c1acc07c5 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -333,10 +333,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 @@ -718,10 +714,10 @@ namespace ts { const noTypePredicate = createTypePredicate(TypePredicateKind.Identifier, "<>", 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((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((signature.declaration).body) ? anyType : getReturnTypeFromBody(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); @@ -12901,8 +12898,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; @@ -13855,7 +13851,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)); } @@ -16680,48 +16676,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; } /** @@ -18755,9 +18725,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) : @@ -19718,7 +19700,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); } @@ -22616,19 +22598,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) { @@ -22639,12 +22610,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); @@ -22652,17 +22637,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) { @@ -22672,11 +22657,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) { @@ -22686,8 +22678,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); @@ -22741,7 +22732,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) { @@ -23098,7 +23089,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); @@ -23117,7 +23118,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 { @@ -23230,7 +23231,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 @@ -23242,7 +23243,7 @@ namespace ts { spliceIndex = index; } - result.splice(spliceIndex, 0, isOptionalCall ? createOptionalCallSignature(signature) : signature); + result.splice(spliceIndex, 0, isOptionalCall ? getOptionalCallSignature(signature) : signature); } } @@ -24187,17 +24188,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, @@ -24207,13 +24212,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 { @@ -24304,12 +24308,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) { @@ -24763,8 +24775,7 @@ namespace ts { typeSymbol ? getDeclaredTypeOfSymbol(typeSymbol) : errorType, /*returnTypePredicate*/ undefined, 1, - /*hasRestparameter*/ false, - /*hasLiteralTypes*/ false + SignatureFlags.None ); } @@ -24803,7 +24814,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)); } @@ -25219,7 +25230,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; } @@ -25238,11 +25249,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'). @@ -25279,7 +25290,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; @@ -25289,7 +25300,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; @@ -25302,7 +25313,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; } @@ -25310,7 +25321,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; } @@ -25331,7 +25342,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 = signature.parameters[i].valueDeclaration; if (declaration.type) { @@ -25365,7 +25376,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(parameter.valueDeclaration)) { @@ -25373,7 +25384,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(parameter.valueDeclaration)) { @@ -25811,7 +25822,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; @@ -27276,7 +27287,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); @@ -27551,7 +27573,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 { @@ -36002,4 +36024,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); + } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 64415c08d2e9f..43c708212dba7 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3443,8 +3443,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; @@ -4655,7 +4654,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 @@ -4672,10 +4686,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 @@ -4686,11 +4696,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; // Generic signature instantiation cache - /* @internal */ - isOptionalCall?: boolean; } export const enum IndexKind { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index fbda386b04b64..335e1e826f163 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -5913,6 +5913,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; } @@ -7312,7 +7320,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; } @@ -7333,7 +7341,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; diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index 307e49c0c443a..b127fa240487f 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -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); diff --git a/src/services/codefixes/inferFromUsage.ts b/src/services/codefixes/inferFromUsage.ts index 11c3fbace614b..1426a84ac274a 100644 --- a/src/services/codefixes/inferFromUsage.ts +++ b/src/services/codefixes/inferFromUsage.ts @@ -1054,7 +1054,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) { diff --git a/src/services/services.ts b/src/services/services.ts index acd06a8ac6dec..3d75cb694a9ab 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -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; } diff --git a/src/services/stringCompletions.ts b/src/services/stringCompletions.ts index 7e106ae54c453..b474f391049a9 100644 --- a/src/services/stringCompletions.ts +++ b/src/services/stringCompletions.ts @@ -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);