diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 3aa20c6c0db..8f7963b249d 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -2674,11 +2674,24 @@ namespace ts { case SyntaxKind.PropertyAccessExpression: return computePropertyAccess(node, subtreeFlags); + case SyntaxKind.CallChain: + return computeCallChain(node, subtreeFlags); + default: return computeOther(node, kind, subtreeFlags); } } + function computeCallChain(node: CallChain, subtreeFlags: TransformFlags) { + let transformFlags = subtreeFlags; + if (node.typeArguments) { + transformFlags |= TransformFlags.AssertTypeScript; + } + + node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; + return transformFlags & ~TransformFlags.ArrayLiteralOrCallOrNewExcludes; + } + function computeCallExpression(node: CallExpression, subtreeFlags: TransformFlags) { let transformFlags = subtreeFlags; const expression = node.expression; @@ -2688,10 +2701,6 @@ namespace ts { transformFlags |= TransformFlags.AssertTypeScript; } - if (node.flags & NodeFlags.Optional) { - transformFlags |= TransformFlags.AssertESNext; - } - if (subtreeFlags & TransformFlags.ContainsSpread || isSuperOrSuperProperty(expression, expressionKind)) { // If the this node contains a SpreadExpression, or is a super call, then it is an ES6 @@ -3136,10 +3145,6 @@ namespace ts { const expression = node.expression; const expressionKind = expression.kind; - if (node.flags & NodeFlags.Optional) { - transformFlags |= TransformFlags.AssertESNext; - } - // If a PropertyAccessExpression starts with a super keyword, then it is // ES6 syntax, and requires a lexical `this` binding. if (expressionKind === SyntaxKind.SuperKeyword) { @@ -3296,6 +3301,12 @@ namespace ts { transformFlags |= TransformFlags.AssertJsx; break; + case SyntaxKind.OptionalExpression: + case SyntaxKind.PropertyAccessChain: + case SyntaxKind.ElementAccessChain: + transformFlags |= TransformFlags.AssertESNext; + break; + case SyntaxKind.NoSubstitutionTemplateLiteral: case SyntaxKind.TemplateHead: case SyntaxKind.TemplateMiddle: @@ -3466,12 +3477,6 @@ namespace ts { break; - case SyntaxKind.ElementAccessExpression: - if (node.flags & NodeFlags.Optional) { - transformFlags |= TransformFlags.AssertESNext; - } - break; - case SyntaxKind.DoStatement: case SyntaxKind.WhileStatement: case SyntaxKind.ForStatement: diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9fb56f5db43..ef33c372283 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -251,7 +251,6 @@ namespace ts { const unknownType = createIntrinsicType(TypeFlags.Any, "unknown"); const undefinedType = createIntrinsicType(TypeFlags.Undefined, "undefined"); const undefinedWideningType = strictNullChecks ? undefinedType : createIntrinsicType(TypeFlags.Undefined | TypeFlags.ContainsWideningType, "undefined"); - const optionalType = createIntrinsicType(TypeFlags.Undefined | TypeFlags.Optional | TypeFlags.ContainsWideningType, "undefined"); const nullType = createIntrinsicType(TypeFlags.Null, "null"); const nullWideningType = strictNullChecks ? nullType : createIntrinsicType(TypeFlags.Null | TypeFlags.ContainsWideningType, "null"); const stringType = createIntrinsicType(TypeFlags.String, "string"); @@ -380,21 +379,19 @@ namespace ts { TypeofNEFunction = 1 << 12, // typeof x !== "function" TypeofNEHostObject = 1 << 13, // typeof x !== "xxx" EQUndefined = 1 << 14, // x === undefined - EQOptional = 1 << 15, - EQNull = 1 << 16, // x === null - EQUndefinedOrNull = 1 << 17, // x === undefined / x === null - NEUndefined = 1 << 18, // x !== undefined - NEOptional = 1 << 19, - NENull = 1 << 20, // x !== null - NEUndefinedOrNull = 1 << 21, // x != undefined / x != null - Truthy = 1 << 22, // x - Falsy = 1 << 23, // !x - Discriminatable = 1 << 24, // May have discriminant property - All = (1 << 25) - 1, + EQNull = 1 << 15, // x === null + EQUndefinedOrNull = 1 << 16, // x === undefined / x === null + NEUndefined = 1 << 17, // x !== undefined + NENull = 1 << 18, // x !== null + NEUndefinedOrNull = 1 << 19, // x != undefined / x != null + Truthy = 1 << 20, // x + Falsy = 1 << 21, // !x + Discriminatable = 1 << 22, // May have discriminant property + All = (1 << 23) - 1, // The following members encode facts about particular kinds of types for use in the getTypeFacts function. // The presence of a particular fact means that the given test is true for some (and possibly all) values // of that kind of type. - BaseStringStrictFacts = TypeofEQString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEOptional | NEUndefinedOrNull, + BaseStringStrictFacts = TypeofEQString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEUndefinedOrNull, BaseStringFacts = BaseStringStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, StringStrictFacts = BaseStringStrictFacts | Truthy | Falsy, StringFacts = BaseStringFacts | Truthy, @@ -402,7 +399,7 @@ namespace ts { EmptyStringFacts = BaseStringFacts, NonEmptyStringStrictFacts = BaseStringStrictFacts | Truthy, NonEmptyStringFacts = BaseStringFacts | Truthy, - BaseNumberStrictFacts = TypeofEQNumber | TypeofNEString | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEOptional | NEUndefinedOrNull, + BaseNumberStrictFacts = TypeofEQNumber | TypeofNEString | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEUndefinedOrNull, BaseNumberFacts = BaseNumberStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, NumberStrictFacts = BaseNumberStrictFacts | Truthy | Falsy, NumberFacts = BaseNumberFacts | Truthy, @@ -410,7 +407,7 @@ namespace ts { ZeroFacts = BaseNumberFacts, NonZeroStrictFacts = BaseNumberStrictFacts | Truthy, NonZeroFacts = BaseNumberFacts | Truthy, - BaseBooleanStrictFacts = TypeofEQBoolean | TypeofNEString | TypeofNENumber | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEOptional | NEUndefinedOrNull, + BaseBooleanStrictFacts = TypeofEQBoolean | TypeofNEString | TypeofNENumber | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEUndefinedOrNull, BaseBooleanFacts = BaseBooleanStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, BooleanStrictFacts = BaseBooleanStrictFacts | Truthy | Falsy, BooleanFacts = BaseBooleanFacts | Truthy, @@ -418,15 +415,14 @@ namespace ts { FalseFacts = BaseBooleanFacts, TrueStrictFacts = BaseBooleanStrictFacts | Truthy, TrueFacts = BaseBooleanFacts | Truthy, - SymbolStrictFacts = TypeofEQSymbol | TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEOptional | NEUndefinedOrNull | Truthy, + SymbolStrictFacts = TypeofEQSymbol | TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEUndefinedOrNull | Truthy, SymbolFacts = SymbolStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, - ObjectStrictFacts = TypeofEQObject | TypeofEQHostObject | TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEFunction | NEUndefined | NENull | NEOptional | NEUndefinedOrNull | Truthy | Discriminatable, + ObjectStrictFacts = TypeofEQObject | TypeofEQHostObject | TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEFunction | NEUndefined | NENull | NEUndefinedOrNull | Truthy | Discriminatable, ObjectFacts = ObjectStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, - FunctionStrictFacts = TypeofEQFunction | TypeofEQHostObject | TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | NEUndefined | NENull | NEOptional | NEUndefinedOrNull | Truthy | Discriminatable, + FunctionStrictFacts = TypeofEQFunction | TypeofEQHostObject | TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | NEUndefined | NENull | NEUndefinedOrNull | Truthy | Discriminatable, FunctionFacts = FunctionStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, - UndefinedFacts = TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | EQUndefined | EQUndefinedOrNull | NENull | NEOptional | Falsy, - OptionalFacts = TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | EQUndefined | EQOptional | EQUndefinedOrNull | NENull | Falsy, - NullFacts = TypeofEQObject | TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEFunction | TypeofNEHostObject | EQNull | EQUndefinedOrNull | NEUndefined | NEOptional | Falsy, + UndefinedFacts = TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | EQUndefined | EQUndefinedOrNull | NENull | Falsy, + NullFacts = TypeofEQObject | TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEFunction | TypeofNEHostObject | EQNull | EQUndefinedOrNull | NEUndefined | Falsy, } const typeofEQFacts = createMapFromTemplate({ @@ -7613,7 +7609,7 @@ namespace ts { return type; } - function getPropertyTypeForIndexType(objectType: Type, indexType: Type, accessNode: ElementAccessExpression | IndexedAccessTypeNode, cacheSymbol: boolean) { + function getPropertyTypeForIndexType(objectType: Type, indexType: Type, accessNode: ElementAccessExpression | ElementAccessChain | IndexedAccessTypeNode, cacheSymbol: boolean) { const accessExpression = accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode : undefined; const propName = indexType.flags & TypeFlags.StringOrNumberLiteral ? escapeLeadingUnderscores("" + (indexType).value) : @@ -7661,7 +7657,7 @@ namespace ts { } } if (accessNode) { - const indexNode = accessNode.kind === SyntaxKind.ElementAccessExpression ? (accessNode).argumentExpression : (accessNode).indexType; + const indexNode = accessNode.kind === SyntaxKind.IndexedAccessType ? (accessNode).indexType : (accessNode).argumentExpression; if (indexType.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral)) { error(indexNode, Diagnostics.Property_0_does_not_exist_on_type_1, "" + (indexType).value, typeToString(objectType)); } @@ -7737,13 +7733,14 @@ namespace ts { return undefined; } - function getIndexedAccessType(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode): Type { + function getIndexedAccessType(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | ElementAccessChain | IndexedAccessTypeNode): Type { // If the index type is generic, or if the object type is generic and doesn't originate in an expression, // we are performing a higher-order index access where we cannot meaningfully access the properties of the // object type. Note that for a generic T and a non-generic K, we eagerly resolve T[K] if it originates in // an expression. This is to preserve backwards compatibility. For example, an element access 'this["foo"]' // has always been resolved eagerly using the constraint type of 'this' at the given location. - if (isGenericIndexType(indexType) || !(accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression) && isGenericObjectType(objectType)) { + if (isGenericIndexType(indexType) || !(accessNode && (accessNode.kind === SyntaxKind.ElementAccessExpression || accessNode.kind === SyntaxKind.ElementAccessChain)) && + isGenericObjectType(objectType)) { if (objectType.flags & TypeFlags.Any) { return objectType; } @@ -10153,10 +10150,6 @@ namespace ts { return strictNullChecks ? getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type; } - function getNonOptionalType(type: Type): Type { - return strictNullChecks ? getTypeWithFacts(type, TypeFacts.NEOptional) : type; - } - /** * Return true if type was inferred from an object literal or written as an object type literal * with no call or construct signatures. @@ -10243,9 +10236,6 @@ namespace ts { function getWidenedType(type: Type): Type { if (type.flags & TypeFlags.RequiresWidening) { - if (type.flags & TypeFlags.Optional) { - return strictNullChecks ? undefinedType : anyType; - } if (type.flags & TypeFlags.Nullable) { return anyType; } @@ -11109,7 +11099,7 @@ namespace ts { strictNullChecks ? TypeFacts.ObjectStrictFacts : TypeFacts.ObjectFacts; } if (flags & (TypeFlags.Void | TypeFlags.Undefined)) { - return flags & TypeFlags.Optional ? TypeFacts.OptionalFacts : TypeFacts.UndefinedFacts; + return TypeFacts.UndefinedFacts; } if (flags & TypeFlags.Null) { return TypeFacts.NullFacts; @@ -14609,14 +14599,14 @@ namespace ts { * Check whether the requested property access is valid. * Returns true if node is a valid property access, and false otherwise. * @param node The node to be checked. - * @param left The left hand side of the property access (e.g.: the super in `super.foo`). + * @param isSuperProperty Whether the left hand side of the property access is `super`. * @param type The type of left. * @param prop The symbol for the right hand side of the property access. */ - function checkPropertyAccessibility(node: PropertyAccessExpression | QualifiedName | VariableLikeDeclaration, left: Expression | QualifiedName, type: Type, prop: Symbol): boolean { + function checkPropertyAccessibility(node: PropertyAccessExpression | PropertyAccessChain | QualifiedName | VariableLikeDeclaration, isSuperProperty: boolean, type: Type, prop: Symbol): boolean { const flags = getDeclarationModifierFlagsFromSymbol(prop); - const errorNode = node.kind === SyntaxKind.PropertyAccessExpression || node.kind === SyntaxKind.VariableDeclaration ? - (node).name : + const errorNode = node.kind === SyntaxKind.PropertyAccessExpression || node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.PropertyAccessChain ? + (node).name : (node).right; if (getCheckFlags(prop) & CheckFlags.ContainsPrivate) { @@ -14625,7 +14615,7 @@ namespace ts { return false; } - if (left.kind === SyntaxKind.SuperKeyword) { + if (isSuperProperty) { // TS 1.0 spec (April 2014): 4.8.2 // - In a constructor, instance member function, instance member accessor, or // instance member variable initializer where this references a derived class instance, @@ -14673,7 +14663,7 @@ namespace ts { // Property is known to be protected at this point // All protected properties of a supertype are accessible in a super access - if (left.kind === SyntaxKind.SuperKeyword) { + if (isSuperProperty) { return true; } @@ -14708,14 +14698,7 @@ namespace ts { return checkNonNullType(checkExpression(node), node); } - function checkNonNullType(type: Type, errorNode: Node, optionality?: NodeFlags): Type { - if (optionality & NodeFlags.OptionalExpression) { - type = getNonNullableType(type); - } - else if (optionality & NodeFlags.OptionalChain) { - type = getNonOptionalType(type); - } - + function checkNonNullType(type: Type, errorNode: Node): Type { const kind = (strictNullChecks ? getFalsyFlags(type) : type.flags) & TypeFlags.Nullable; if (kind) { error(errorNode, kind & TypeFlags.Undefined ? kind & TypeFlags.Null ? @@ -14729,51 +14712,20 @@ namespace ts { } function checkPropertyAccessExpression(node: PropertyAccessExpression) { - return checkPropertyAccessExpressionOrQualifiedName(node, node.expression, node.name); + return checkPropertyAccessExpressionOrQualifiedName(node, checkNonNullExpression(node.expression), node.name, node.expression.kind === SyntaxKind.SuperKeyword); } function checkQualifiedName(node: QualifiedName) { - return checkPropertyAccessExpressionOrQualifiedName(node, node.left, node.right); + return checkPropertyAccessExpressionOrQualifiedName(node, checkNonNullExpression(node.left), node.right, /*isSuperProperty*/ false); } - // function checkOptionality(node: Node) { - // if (node.flags & NodeFlags.OptionalExpression) { - // if (!compilerOptions.experimentalOptionalChaining) { - // error(node, Diagnostics.Experimental_support_for_optional_chaining_is_a_feature_that_is_subject_to_change_in_a_future_release_Set_the_experimentalOptionalChaining_option_to_remove_this_warning); - // } - // return strictNullChecks; - // } - // return false; - // } - - function propagateOptionalChain(type: Type, sourceType: Type, optionality: NodeFlags) { - const expectedFacts = optionality & NodeFlags.OptionalExpression ? TypeFacts.EQUndefinedOrNull : TypeFacts.EQOptional; - return optionality && strictNullChecks && getTypeFacts(sourceType) & expectedFacts - ? getUnionType([type, optionalType]) - : type; - } - - function propagateOptionalChainSignature(signature: Signature, sourceType: Type, optionality: NodeFlags) { - const expectedFacts = optionality & NodeFlags.OptionalExpression ? TypeFacts.EQUndefinedOrNull : TypeFacts.EQOptional; - if (optionality && strictNullChecks && getTypeFacts(sourceType) & expectedFacts) { - signature = cloneSignature(signature); - signature.resolvedReturnType = getUnionType([getReturnTypeOfSignature(signature), optionalType]); - } - return signature; - } - - function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, right: Identifier) { - // if a node is an OptionalExpression, use its non-null type - const optionality = node.flags & NodeFlags.Optional; - const sourceType = checkExpression(left); - const type = checkNonNullType(sourceType, left, optionality); - - if (isTypeAny(type) || type === silentNeverType) { - return type; + function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | PropertyAccessChain | QualifiedName, leftType: Type, right: Identifier, isSuperProperty: boolean) { + if (isTypeAny(leftType) || leftType === silentNeverType) { + return leftType; } - const apparentType = getApparentType(getWidenedType(type)); - if (apparentType === unknownType || (type.flags & TypeFlags.TypeParameter && isTypeAny(apparentType))) { + const apparentType = getApparentType(getWidenedType(leftType)); + if (apparentType === unknownType || (leftType.flags & TypeFlags.TypeParameter && isTypeAny(apparentType))) { // handle cases when type is Type parameter with invalid or any constraint return apparentType; } @@ -14787,7 +14739,7 @@ namespace ts { return indexInfo.type; } if (right.escapedText && !checkAndReportErrorForExtendingInterface(node)) { - reportNonexistentProperty(right, type.flags & TypeFlags.TypeParameter && (type as TypeParameter).isThisType ? apparentType : type); + reportNonexistentProperty(right, leftType.flags & TypeFlags.TypeParameter && (leftType as TypeParameter).isThisType ? apparentType : leftType); } return unknownType; } @@ -14798,7 +14750,7 @@ namespace ts { getNodeLinks(node).resolvedSymbol = prop; - checkPropertyAccessibility(node, left, apparentType, prop); + checkPropertyAccessibility(node, isSuperProperty, apparentType, prop); const propType = getDeclaredOrApparentType(prop, node); const assignmentKind = getAssignmentTargetKind(node); @@ -14816,14 +14768,13 @@ namespace ts { if (node.kind !== SyntaxKind.PropertyAccessExpression || assignmentKind === AssignmentKind.Definite || !(prop.flags & (SymbolFlags.Variable | SymbolFlags.Property | SymbolFlags.Accessor)) && !(prop.flags & SymbolFlags.Method && propType.flags & TypeFlags.Union)) { - return propagateOptionalChain(propType, sourceType, optionality); + return propType; } const flowType = getFlowTypeOfReference(node, propType); - const resultType = assignmentKind ? getBaseTypeOfLiteralType(flowType) : flowType; - return propagateOptionalChain(resultType, sourceType, optionality); + return assignmentKind ? getBaseTypeOfLiteralType(flowType) : flowType; } - function checkPropertyNotUsedBeforeDeclaration(prop: Symbol, node: PropertyAccessExpression | QualifiedName, right: Identifier): void { + function checkPropertyNotUsedBeforeDeclaration(prop: Symbol, node: PropertyAccessExpression | PropertyAccessChain | QualifiedName, right: Identifier): void { const { valueDeclaration } = prop; if (!valueDeclaration) { return; @@ -15021,7 +14972,7 @@ namespace ts { if (type !== unknownType && !isTypeAny(type)) { const prop = getPropertyOfType(type, propertyName); if (prop) { - return checkPropertyAccessibility(node, left, type, prop); + return checkPropertyAccessibility(node, left.kind === SyntaxKind.SuperKeyword, type, prop); } // In js files properties of unions are allowed in completion @@ -15089,10 +15040,11 @@ namespace ts { } function checkIndexedAccess(node: ElementAccessExpression): Type { - const optionality = node.flags & NodeFlags.Optional; - const sourceType = checkExpression(node.expression); - const objectType = checkNonNullType(sourceType, node.expression, optionality); + const objectType = checkNonNullExpression(node.expression); + return checkIndexedAccessWorker(node, objectType); + } + function checkIndexedAccessWorker(node: ElementAccessExpression | ElementAccessChain, objectType: Type) { const indexExpression = node.argumentExpression; if (!indexExpression) { const sourceFile = getSourceFileOfNode(node); @@ -15120,8 +15072,7 @@ namespace ts { return unknownType; } - const resultType = checkIndexedAccessIndexType(getIndexedAccessType(objectType, indexType, node), node); - return propagateOptionalChain(resultType, objectType, optionality); + return checkIndexedAccessIndexType(getIndexedAccessType(objectType, indexType, node), node); } function checkThatExpressionIsProperSymbolReference(expression: Expression, expressionType: Type, reportError: boolean): boolean { @@ -15166,6 +15117,34 @@ namespace ts { return true; } + function getLeftTypeOfOptionalChain(node: OptionalChain) { + if (node.chain) return checkOptionalChain(node.chain); + const optionalExpression = findAncestor(node, isOptionalExpression); + const type = checkExpressionCached(optionalExpression.expression); + return getNonNullableType(type); + } + + function checkOptionalChain(node: OptionalChain): Type { + const objectType = getLeftTypeOfOptionalChain(node); + switch (node.kind) { + case SyntaxKind.PropertyAccessChain: + return checkPropertyAccessExpressionOrQualifiedName(node, objectType, node.name, /*isSuperProperty*/ false); + case SyntaxKind.ElementAccessChain: + return checkIndexedAccessWorker(node, objectType); + case SyntaxKind.CallChain: + return checkCallExpression(node); + } + } + + function checkOptionalExpression(node: OptionalExpression) { + const type = checkExpression(node.expression); + const chainType = checkOptionalChain(node.chain); + if ((strictNullChecks ? getFalsyFlags(type) : type.flags) & TypeFlags.Nullable) { + return getNullableType(chainType, TypeFlags.Undefined); + } + return chainType; + } + function callLikeExpressionMayHaveTypeArguments(node: CallLikeExpression): node is CallExpression | NewExpression { // TODO: Also include tagged templates (https://github.com/Microsoft/TypeScript/issues/11947) return isCallOrNewExpression(node); @@ -15383,7 +15362,10 @@ namespace ts { // example, given a 'function wrap(cb: (x: T) => U): (x: T) => U' and a call expression // 'let f: (x: string) => number = wrap(s => s.length)', we infer from the declared type of 'f' to the // return type of 'wrap'. - if (node.kind !== SyntaxKind.Decorator) { + if (node.kind === SyntaxKind.CallChain) { + sys.write("TODO: getContextualTypeOfCallChain"); + } + else if (node.kind !== SyntaxKind.Decorator) { const contextualType = getContextualType(node); if (contextualType) { // We clone the contextual mapper to avoid disturbing a resolution in progress for an @@ -16168,8 +16150,8 @@ namespace ts { return maxParamsIndex; } - function resolveCallExpression(node: CallExpression, candidatesOutArray: Signature[]): Signature { - if (node.expression.kind === SyntaxKind.SuperKeyword) { + function resolveCallExpression(node: CallExpression | CallChain, candidatesOutArray: Signature[]): Signature { + if (node.kind !== SyntaxKind.CallChain && node.expression.kind === SyntaxKind.SuperKeyword) { const superType = checkSuperExpression(node.expression); if (superType !== unknownType) { // In super call, the candidate signatures are the matching arity signatures of the base constructor function instantiated @@ -16183,9 +16165,7 @@ namespace ts { return resolveUntypedCall(node); } - const optionality = node.flags & NodeFlags.Optional; - const sourceType = checkExpression(node.expression); - const funcType = checkNonNullType(sourceType, node.expression, optionality); + const funcType = node.kind === SyntaxKind.CallChain ? getLeftTypeOfOptionalChain(node) : checkNonNullExpression(node.expression); if (funcType === silentNeverType) { return silentNeverSignature; } @@ -16226,9 +16206,7 @@ namespace ts { } return resolveErrorCall(node); } - - const signature = resolveCall(node, callSignatures, candidatesOutArray); - return propagateOptionalChainSignature(signature, sourceType, optionality); + return resolveCall(node, callSignatures, candidatesOutArray); } /** @@ -16495,7 +16473,8 @@ namespace ts { function resolveSignature(node: CallLikeExpression, candidatesOutArray?: Signature[]): Signature { switch (node.kind) { case SyntaxKind.CallExpression: - return resolveCallExpression(node, candidatesOutArray); + case SyntaxKind.CallChain: + return resolveCallExpression(node, candidatesOutArray); case SyntaxKind.NewExpression: return resolveNewExpression(node, candidatesOutArray); case SyntaxKind.TaggedTemplateExpression: @@ -16590,13 +16569,14 @@ namespace ts { * @param node The call/new expression to be checked. * @returns On success, the expression's signature's return type. On failure, anyType. */ - function checkCallExpression(node: CallExpression | NewExpression): Type { + function checkCallExpression(node: CallExpression | CallChain | NewExpression): Type { // Grammar checking; stop grammar-checking if checkGrammarTypeArguments return true checkGrammarTypeArguments(node, node.typeArguments) || checkGrammarArguments(node.arguments); const signature = getResolvedSignature(node); - if (node.expression.kind === SyntaxKind.SuperKeyword) { + const isSuper = node.kind !== SyntaxKind.CallChain && node.expression.kind === SyntaxKind.SuperKeyword; + if (isSuper) { return voidType; } @@ -18225,6 +18205,8 @@ namespace ts { return checkPropertyAccessExpression(node); case SyntaxKind.ElementAccessExpression: return checkIndexedAccess(node); + case SyntaxKind.OptionalExpression: + return checkOptionalExpression(node); case SyntaxKind.CallExpression: if ((node).expression.kind === SyntaxKind.ImportKeyword) { return checkImportCallExpression(node); @@ -18973,7 +18955,7 @@ namespace ts { forEach(node.types, checkSourceElement); } - function checkIndexedAccessIndexType(type: Type, accessNode: ElementAccessExpression | IndexedAccessTypeNode) { + function checkIndexedAccessIndexType(type: Type, accessNode: ElementAccessExpression | ElementAccessChain | IndexedAccessTypeNode) { if (!(type.flags & TypeFlags.IndexedAccess)) { return type; } @@ -20436,7 +20418,7 @@ namespace ts { const property = getPropertyOfType(parentType, getTextOfPropertyName(name)); markPropertyAsReferenced(property, /*nodeForCheckWriteOnly*/ undefined); // A destructuring is never a write-only reference. if (parent.initializer && property) { - checkPropertyAccessibility(parent, parent.initializer, parentType, property); + checkPropertyAccessibility(parent, /*isSuperProperty*/ false, parentType, property); } } diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 3de8c39eb2f..4571d92405c 100755 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -583,6 +583,14 @@ namespace ts { case SyntaxKind.BindingElement: return emitBindingElement(node); + // Optional chains + case SyntaxKind.PropertyAccessChain: + return emitPropertyAccessChain(node); + case SyntaxKind.ElementAccessChain: + return emitElementAccessChain(node); + case SyntaxKind.CallChain: + return emitCallChain(node); + // Misc case SyntaxKind.TemplateSpan: return emitTemplateSpan(node); @@ -768,6 +776,8 @@ namespace ts { return emitPropertyAccessExpression(node); case SyntaxKind.ElementAccessExpression: return emitElementAccessExpression(node); + case SyntaxKind.OptionalExpression: + return emitOptionalExpression(node); case SyntaxKind.CallExpression: return emitCallExpression(node); case SyntaxKind.NewExpression: @@ -1195,13 +1205,12 @@ namespace ts { } function emitPropertyAccessExpression(node: PropertyAccessExpression) { - const isOptionalExpression = node.flags & NodeFlags.OptionalExpression; let indentBeforeDot = false; let indentAfterDot = false; if (!(getEmitFlags(node) & EmitFlags.NoIndentation)) { const dotRangeStart = node.expression.end; const dotRangeEnd = skipTrivia(currentSourceFile.text, node.expression.end) + 1; - const dotToken = createToken(isOptionalExpression ? SyntaxKind.QuestionDotToken : SyntaxKind.DotToken); + const dotToken = createToken(SyntaxKind.DotToken); dotToken.pos = dotRangeStart; dotToken.end = dotRangeEnd; indentBeforeDot = needsIndentation(node, node.expression, dotToken); @@ -1211,8 +1220,8 @@ namespace ts { emitExpression(node.expression); increaseIndentIf(indentBeforeDot); - const shouldEmitDotDot = !isOptionalExpression && !indentBeforeDot && needsDotDotForPropertyAccess(node.expression); - write(shouldEmitDotDot ? ".." : isOptionalExpression ? "?." : "."); + const shouldEmitDotDot = !indentBeforeDot && needsDotDotForPropertyAccess(node.expression); + write(shouldEmitDotDot ? ".." : "."); increaseIndentIf(indentAfterDot); emit(node.name); @@ -1241,16 +1250,55 @@ namespace ts { function emitElementAccessExpression(node: ElementAccessExpression) { emitExpression(node.expression); - write(node.flags & NodeFlags.OptionalExpression ? "?.[" : "["); + write("["); emitExpression(node.argumentExpression); write("]"); } - function emitCallExpression(node: CallExpression) { + function emitOptionalExpression(node: OptionalExpression) { emitExpression(node.expression); - if (node.flags & NodeFlags.OptionalExpression) { + emit(node.chain); + } + + function emitPropertyAccessChain(node: PropertyAccessChain) { + if (node.chain) { + emit(node.chain); + write("."); + } + else { write("?."); } + + emit(node.name); + } + + function emitElementAccessChain(node: ElementAccessChain) { + if (node.chain) { + emit(node.chain); + } + else { + write("?."); + } + + write("["); + emitExpression(node.argumentExpression); + write("]"); + } + + function emitCallChain(node: CallChain) { + if (node.chain) { + emit(node.chain); + } + else { + write("?."); + } + + emitTypeArguments(node, node.typeArguments); + emitExpressionList(node, node.arguments, ListFormat.CallExpressionArguments); + } + + function emitCallExpression(node: CallExpression) { + emitExpression(node.expression); emitTypeArguments(node, node.typeArguments); emitExpressionList(node, node.arguments, ListFormat.CallExpressionArguments); } diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 88a0dff1d8c..8a50ca40b09 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -859,7 +859,6 @@ namespace ts { export function createPropertyAccess(expression: Expression, name: string | Identifier) { const node = createSynthesizedNode(SyntaxKind.PropertyAccessExpression); - if (expression.flags & NodeFlags.Optional) node.flags |= NodeFlags.OptionalChain; node.expression = parenthesizeForAccess(expression); node.name = asName(name); setEmitFlags(node, EmitFlags.NoIndentation); @@ -877,7 +876,6 @@ namespace ts { export function createElementAccess(expression: Expression, index: number | Expression) { const node = createSynthesizedNode(SyntaxKind.ElementAccessExpression); - if (expression.flags & NodeFlags.Optional) node.flags |= NodeFlags.OptionalChain; node.expression = parenthesizeForAccess(expression); node.argumentExpression = asExpression(index); return node; @@ -890,9 +888,66 @@ namespace ts { : node; } + export function createOptionalExpression(expression: Expression, chain: OptionalChain) { + const node = createSynthesizedNode(SyntaxKind.ElementAccessExpression); + node.expression = parenthesizeForAccess(expression); + node.chain = chain; + return node; + } + + export function updateOptionalExpression(node: OptionalExpression, expression: Expression, chain: OptionalChain) { + return node.expression !== expression + || node.chain !== chain + ? updateNode(createOptionalExpression(expression, chain), node) + : node; + } + + export function createPropertyAccessChain(chain: OptionalChain | undefined, name: Identifier) { + const node = createSynthesizedNode(SyntaxKind.PropertyAccessChain); + node.chain = chain; + node.name = name; + return node; + } + + export function updatePropertyAccessChain(node: PropertyAccessChain, chain: OptionalChain | undefined, name: Identifier) { + return node.chain !== chain + || node.name !== name + ? updateNode(createPropertyAccessChain(chain, name), node) + : node; + } + + export function createElementAccessChain(chain: OptionalChain | undefined, argumentExpression: Expression) { + const node = createSynthesizedNode(SyntaxKind.ElementAccessChain); + node.chain = chain; + node.argumentExpression = argumentExpression; + return node; + } + + export function updateElementAccessChain(node: ElementAccessChain, chain: OptionalChain | undefined, argumentExpression: Expression) { + return node.chain !== chain + || node.argumentExpression !== argumentExpression + ? updateNode(createElementAccessChain(chain, argumentExpression), node) + : node; + } + + export function createCallChain(chain: OptionalChain | undefined, typeArguments: ReadonlyArray | undefined, argumentList: ReadonlyArray) { + const node = createSynthesizedNode(SyntaxKind.CallChain); + node.chain = chain; + node.typeArguments = asNodeArray(typeArguments); + node.arguments = createNodeArray(argumentList); + return node; + } + + export function updateCallChain(node: CallChain, chain: OptionalChain | undefined, typeArguments: ReadonlyArray | undefined, argumentList: ReadonlyArray) { + return node.chain !== chain + || node.typeArguments !== typeArguments + || node.arguments !== argumentList + ? updateNode(createCallChain(chain, typeArguments, argumentList), node) + : node; + } + export function createCall(expression: Expression, typeArguments: ReadonlyArray | undefined, argumentsArray: ReadonlyArray) { const node = createSynthesizedNode(SyntaxKind.CallExpression); - if (expression.flags & NodeFlags.Optional) node.flags |= NodeFlags.OptionalChain; node.expression = parenthesizeForAccess(expression); node.typeArguments = asNodeArray(typeArguments); node.arguments = parenthesizeListElements(createNodeArray(argumentsArray)); @@ -2505,10 +2560,6 @@ namespace ts { return createBinary(left, SyntaxKind.EqualsToken, right); } - export function createEquality(left: Expression, right: Expression) { - return createBinary(left, SyntaxKind.EqualsEqualsToken, right); - } - export function createStrictEquality(left: Expression, right: Expression) { return createBinary(left, SyntaxKind.EqualsEqualsEqualsToken, right); } diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 85ab79b3c35..f23d4946718 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -170,6 +170,19 @@ namespace ts { case SyntaxKind.ElementAccessExpression: return visitNode(cbNode, (node).expression) || visitNode(cbNode, (node).argumentExpression); + case SyntaxKind.OptionalExpression: + return visitNode(cbNode, (node).expression) || + visitNode(cbNode, (node).chain); + case SyntaxKind.PropertyAccessChain: + return visitNode(cbNode, (node).chain) || + visitNode(cbNode, (node).name); + case SyntaxKind.ElementAccessChain: + return visitNode(cbNode, (node).chain) || + visitNode(cbNode, (node).argumentExpression); + case SyntaxKind.CallChain: + return visitNode(cbNode, (node).chain) || + visitNodes(cbNode, cbNodes, (node).typeArguments) || + visitNodes(cbNode, cbNodes, (node).arguments); case SyntaxKind.CallExpression: case SyntaxKind.NewExpression: return visitNode(cbNode, (node).expression) || @@ -2532,11 +2545,6 @@ namespace ts { return token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken; } - function nextTokenIsOpenBracket() { - nextToken(); - return token() === SyntaxKind.OpenBracketToken; - } - function parseTypeLiteral(): TypeLiteralNode { const node = createNode(SyntaxKind.TypeLiteral); node.members = parseObjectTypeMembers(); @@ -3869,7 +3877,7 @@ namespace ts { // Now, we *may* be complete. However, we might have consumed the start of a // CallExpression. As such, we need to consume the rest of it here to be complete. - return parseCallExpressionRest(expression); + return parseOptionalExpressionRest(parseCallExpressionRest(expression)); } function parseMemberExpressionOrHigher(): MemberExpression { @@ -4191,22 +4199,9 @@ namespace ts { function parseMemberExpressionRest(expression: LeftHandSideExpression): MemberExpression { while (true) { - const isOptionalExpression = token() === SyntaxKind.QuestionDotToken; - const isOptionalCall = isOptionalExpression && lookAhead(nextTokenIsOpenParenOrLessThan); - if (isOptionalCall) { - // In an optional-chaining call or new expression, we defer parsing `.?` to parseCallExpressionRest. - return expression; - } - - let flags: NodeFlags = NodeFlags.None; - if (isOptionalExpression) flags |= NodeFlags.OptionalExpression; - if (expression.flags & NodeFlags.Optional) flags |= NodeFlags.OptionalChain; - - const isOptionalPropertyAccess = isOptionalExpression && !lookAhead(nextTokenIsOpenBracket); - if (isOptionalPropertyAccess || token() === SyntaxKind.DotToken) { - nextToken(); + const dotToken = parseOptionalToken(SyntaxKind.DotToken); + if (dotToken) { const propertyAccess = createNode(SyntaxKind.PropertyAccessExpression, expression.pos); - propertyAccess.flags = flags; propertyAccess.expression = expression; propertyAccess.name = parseRightSideOfDot(/*allowIdentifierNames*/ true); expression = finishNode(propertyAccess); @@ -4216,18 +4211,14 @@ namespace ts { if (token() === SyntaxKind.ExclamationToken && !scanner.hasPrecedingLineBreak()) { nextToken(); const nonNullExpression = createNode(SyntaxKind.NonNullExpression, expression.pos); - nonNullExpression.flags = flags; nonNullExpression.expression = expression; expression = finishNode(nonNullExpression); continue; } // when in the [Decorator] context, we do not parse ElementAccess as it could be part of a ComputedPropertyName - // however, `?.[` is unambiguously *not* a ComputedPropertyName. - const isOptionalElementAccess = isOptionalExpression && !isOptionalPropertyAccess; - if (isOptionalElementAccess || (!inDecoratorContext() && parseOptional(SyntaxKind.OpenBracketToken))) { + if (!inDecoratorContext() && parseOptional(SyntaxKind.OpenBracketToken)) { const indexedAccess = createNode(SyntaxKind.ElementAccessExpression, expression.pos); - indexedAccess.flags = flags; indexedAccess.expression = expression; // It's not uncommon for a user to write: "new Type[]". @@ -4247,7 +4238,6 @@ namespace ts { if (token() === SyntaxKind.NoSubstitutionTemplateLiteral || token() === SyntaxKind.TemplateHead) { const tagExpression = createNode(SyntaxKind.TaggedTemplateExpression, expression.pos); - tagExpression.flags = flags; tagExpression.tag = expression; tagExpression.template = token() === SyntaxKind.NoSubstitutionTemplateLiteral ? parseLiteralNode() @@ -4263,44 +4253,77 @@ namespace ts { function parseCallExpressionRest(expression: LeftHandSideExpression): LeftHandSideExpression { while (true) { expression = parseMemberExpressionRest(expression); - - // If we parsed MemberExpression and see a `?.` here, then we are part of an optional chain. - const isOptionalExpression = parseOptional(SyntaxKind.QuestionDotToken); - - let flags: NodeFlags = NodeFlags.None; - if (isOptionalExpression) flags |= NodeFlags.OptionalExpression; - if (expression.flags & NodeFlags.Optional) flags |= NodeFlags.OptionalChain; - - let typeArguments: NodeArray; if (token() === SyntaxKind.LessThanToken) { - if (isOptionalExpression) { - // If we are part of an optional chain, this *must* be a generic call. - typeArguments = parseTypeArgumentsInExpression(); - } - else { - // See if this is the start of a generic invocation. If so, consume it and - // keep checking for postfix expressions. Otherwise, it's just a '<' that's - // part of an arithmetic expression. Break out so we consume it higher in the - // stack. - typeArguments = tryParse(parseTypeArgumentsInExpression); - if (!typeArguments) { - return expression; - } + // See if this is the start of a generic invocation. If so, consume it and + // keep checking for postfix expressions. Otherwise, it's just a '<' that's + // part of an arithmetic expression. Break out so we consume it higher in the + // stack. + const typeArguments = tryParse(parseTypeArgumentsInExpression); + if (!typeArguments) { + return expression; } + + const callExpr = createNode(SyntaxKind.CallExpression, expression.pos); + callExpr.expression = expression; + callExpr.typeArguments = typeArguments; + callExpr.arguments = parseArgumentList(); + expression = finishNode(callExpr); + continue; } - else if (token() !== SyntaxKind.LessThanToken) { - return expression; + else if (token() === SyntaxKind.OpenParenToken) { + const callExpr = createNode(SyntaxKind.CallExpression, expression.pos); + callExpr.expression = expression; + callExpr.arguments = parseArgumentList(); + expression = finishNode(callExpr); + continue; } - const callExpr = createNode(SyntaxKind.CallExpression, expression.pos); - callExpr.flags = flags; - callExpr.expression = expression; - callExpr.typeArguments = typeArguments; - callExpr.arguments = parseArgumentList(); - expression = finishNode(callExpr); + return expression; } } + function parseOptionalExpressionRest(expression: LeftHandSideExpression) { + while (token() === SyntaxKind.QuestionDotToken) { + const fullStart = getNodePos(); + nextToken(); + + let chain: OptionalChain; + while (true) { + if (parseOptional(SyntaxKind.OpenBracketToken)) { + const elementAccessChain = createNode(SyntaxKind.ElementAccessChain, fullStart) as ElementAccessChain; + elementAccessChain.chain = chain; + elementAccessChain.argumentExpression = parseExpression(); + parseExpected(SyntaxKind.CloseBracketToken); + chain = finishNode(elementAccessChain); + continue; + } + else if (token() === SyntaxKind.LessThanToken || token() === SyntaxKind.OpenParenToken) { + const callChain = createNode(SyntaxKind.CallChain, fullStart) as CallChain; + callChain.chain = chain; + callChain.typeArguments = parseTypeArgumentsInExpression(); + callChain.arguments = parseArgumentList(); + chain = finishNode(callChain); + continue; + } + else if (!chain || parseOptional(SyntaxKind.DotToken)) { + const propertyAccessChain = createNode(SyntaxKind.PropertyAccessChain, fullStart) as PropertyAccessChain; + propertyAccessChain.chain = chain; + propertyAccessChain.name = parseRightSideOfDot(/*allowIdentifierNames*/ true); + chain = finishNode(propertyAccessChain); + continue; + } + + const node = createNode(SyntaxKind.OptionalExpression, expression.pos) as OptionalExpression; + node.expression = expression; + node.chain = chain; + expression = finishNode(node); + break; + } + } + + return expression; + } + function parseArgumentList() { parseExpected(SyntaxKind.OpenParenToken); const result = parseDelimitedList(ParsingContext.ArgumentExpressions, parseArgumentExpression); @@ -4338,7 +4361,6 @@ namespace ts { case SyntaxKind.ColonToken: // foo: case SyntaxKind.SemicolonToken: // foo; case SyntaxKind.QuestionToken: // foo? - case SyntaxKind.QuestionDotToken: // foo?. case SyntaxKind.EqualsEqualsToken: // foo == case SyntaxKind.EqualsEqualsEqualsToken: // foo === case SyntaxKind.ExclamationEqualsToken: // foo != diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index 3ceb1102a64..17d90d8cf17 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -105,12 +105,8 @@ namespace ts { return visitParenthesizedExpression(node as ParenthesizedExpression, noDestructuringValue); case SyntaxKind.CatchClause: return visitCatchClause(node as CatchClause); - case SyntaxKind.CallExpression: - return visitCallExpression(node as CallExpression); - case SyntaxKind.PropertyAccessExpression: - return visitPropertyAccessExpression(node as PropertyAccessExpression); - case SyntaxKind.ElementAccessExpression: - return visitElementAccessExpression(node as ElementAccessExpression); + case SyntaxKind.OptionalExpression: + return visitOptionalExpression(node as OptionalExpression); default: return visitEachChild(node, visitor, context); } @@ -726,101 +722,43 @@ namespace ts { return statements; } - function getEffectiveExpressionOfOptionalExpression(node: PropertyAccessExpression | ElementAccessExpression | CallExpression): Expression | undefined { - if (node.flags & NodeFlags.OptionalExpression) { - return node.expression; - } - - if (node.flags & NodeFlags.OptionalChain) { - if (isPropertyAccessExpression(node.expression) || - isElementAccessExpression(node.expression) || - isCallExpression(node.expression)) { - return getEffectiveExpressionOfOptionalExpression(node.expression); - } + function visitOptionalChain(node: OptionalChain | undefined, expression: Expression) { + if (!node) return expression; + switch (node.kind) { + case SyntaxKind.PropertyAccessChain: + const propertyAccessExpression = createPropertyAccess( + visitOptionalChain(node.chain, expression), + visitNode(node.name, visitor, isIdentifier)); + return propertyAccessExpression; + case SyntaxKind.ElementAccessChain: + const elementAccessExpression = createElementAccess( + visitOptionalChain(node.chain, expression), + visitNode(node.argumentExpression, visitor, isExpression)); + return elementAccessExpression; + case SyntaxKind.CallChain: + const callExpression = createCall( + visitOptionalChain(node.chain, expression), + /*typeArguments*/ undefined, + visitNodes(node.arguments, visitor, isExpression)); + return callExpression; } } - type OptionalChain = PropertyAccessExpression | ElementAccessExpression | CallExpression; - - function isOptionalExpression(node: OptionalChain) { - return !!(node.flags & NodeFlags.OptionalExpression); - } - - function isOptionalChain(node: Expression): node is OptionalChain { - return isPropertyAccessExpression(node) - || isElementAccessExpression(node) - || isCallExpression(node); - } - - function visitOptionalChain(node: OptionalChain) { - let chain = node; - const stack: OptionalChain[] = [chain]; - while (!isOptionalExpression(chain) && isOptionalChain(chain.expression)) { - chain = chain.expression; - stack.push(chain); - } - + function visitOptionalExpression(node: OptionalExpression) { + const root = visitNode(node.expression, visitor, isExpression); const temp = createTempVariable(hoistVariableDeclaration); - const root = visitNode(chain.expression, visitor, isExpression); - setOriginalNode(temp, root); - setSourceMapRange(temp, root); - setEmitFlags(temp, EmitFlags.NoComments); - - let expression: LeftHandSideExpression = temp; - while (stack.length) { - chain = stack.pop(); - switch (chain.kind) { - case SyntaxKind.PropertyAccessExpression: - expression = createPropertyAccess(expression, visitNode(chain.name, visitor, isIdentifier)); - break; - case SyntaxKind.ElementAccessExpression: - expression = createElementAccess(expression, visitNode(chain.argumentExpression, visitor, isExpression)); - break; - case SyntaxKind.CallExpression: - expression = createCall(expression, /*typeArguments*/ undefined, visitNodes(chain.arguments, visitor, isExpression)); - break; - } - - setOriginalNode(expression, chain); - setSourceMapRange(expression, chain); - setCommentRange(expression, chain); - setEmitFlags(expression, EmitFlags.NoLeadingComments); - } - - const condition = createEquality(createAssignment(temp, root), createNull()); - setSourceMapRange(condition, root); - - const voidZero = createVoidZero(); - setSourceMapRange(voidZero, root); - - const conditional = createConditional(condition, voidZero, expression); - setOriginalNode(conditional, node); - setSourceMapRange(conditional, node); - setCommentRange(conditional, node); + // setSourceMapRange(temp, root); + const condition = createLogicalOr( + createStrictEquality(createAssignment(temp, root), createNull()), + createStrictEquality(temp, createVoidZero())); + // setSourceMapRange(condition, root); + const chain = visitOptionalChain(node.chain, temp); + const conditional = createConditional(condition, createVoidZero(), chain); + // setSourceMapRange(conditional, node); + // setCommentRange(conditional, node); return conditional; } - function visitPropertyAccessExpression(node: PropertyAccessExpression) { - if (node.flags & NodeFlags.Optional) { - return visitOptionalChain(node); - } - return visitEachChild(node, visitor, context); - } - - function visitElementAccessExpression(node: ElementAccessExpression) { - if (node.flags & NodeFlags.Optional) { - return visitOptionalChain(node); - } - return visitEachChild(node, visitor, context); - } - - function visitCallExpression(node: CallExpression) { - if (node.flags & NodeFlags.Optional) { - return visitOptionalChain(node); - } - return visitEachChild(node, visitor, context); - } - function enableSubstitutionForAsyncMethodsWithSuper() { if ((enabledSubstitutions & ESNextSubstitutionFlags.AsyncMethodsWithSuper) === 0) { enabledSubstitutions |= ESNextSubstitutionFlags.AsyncMethodsWithSuper; diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index 8640c642675..81b4399f15a 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -513,6 +513,9 @@ namespace ts { // TypeScript namespace or external module import. return visitImportEqualsDeclaration(node); + case SyntaxKind.CallChain: + return visitCallChain(node); + default: Debug.failBadSyntaxKind(node); return visitEachChild(node, visitor, context); @@ -2452,6 +2455,15 @@ namespace ts { return createPartiallyEmittedExpression(expression, node); } + function visitCallChain(node: CallChain) { + return updateCallChain( + node, + visitNode(node.chain, visitor, isOptionalChain), + /*typeArguments*/ undefined, + visitNodes(node.arguments, visitor, isExpression) + ); + } + function visitCallExpression(node: CallExpression) { return updateCall( node, diff --git a/src/compiler/types.ts b/src/compiler/types.ts index c5402c01feb..4a5cc2ff4aa 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -251,6 +251,7 @@ namespace ts { PropertyAccessExpression, ElementAccessExpression, CallExpression, + OptionalExpression, NewExpression, TaggedTemplateExpression, TypeAssertionExpression, @@ -275,6 +276,11 @@ namespace ts { NonNullExpression, MetaProperty, + // Optional chains + PropertyAccessChain, + ElementAccessChain, + CallChain, + // Misc TemplateSpan, SemicolonClassElement, @@ -383,7 +389,6 @@ namespace ts { CommaListExpression, MergeDeclarationMarker, EndOfDeclarationMarker, - OptionalExpression, // Enum value count Count, @@ -427,22 +432,20 @@ namespace ts { NestedNamespace = 1 << 2, // Namespace declaration Synthesized = 1 << 3, // Node was synthesized during transformation Namespace = 1 << 4, // Namespace declaration - OptionalExpression = 1 << 5, // OptionalExpression (?.) - OptionalChain = 1 << 6, // Optional chaining (expressions following ?.) - ExportContext = 1 << 7, // Export context (initialized by binding) - ContainsThis = 1 << 8, // Interface contains references to "this" - HasImplicitReturn = 1 << 9, // If function implicitly returns on one of codepaths (initialized by binding) - HasExplicitReturn = 1 << 10, // If function has explicit reachable return on one of codepaths (initialized by binding) - GlobalAugmentation = 1 << 11, // Set if module declaration is an augmentation for the global scope - HasAsyncFunctions = 1 << 12, // If the file has async functions (initialized by binding) - DisallowInContext = 1 << 13, // If node was parsed in a context where 'in-expressions' are not allowed - YieldContext = 1 << 14, // If node was parsed in the 'yield' context created when parsing a generator - DecoratorContext = 1 << 15, // If node was parsed as part of a decorator - AwaitContext = 1 << 16, // If node was parsed in the 'await' context created when parsing an async function - ThisNodeHasError = 1 << 17, // If the parser encountered an error when parsing the code that created this node - JavaScriptFile = 1 << 18, // If node was parsed in a JavaScript - ThisNodeOrAnySubNodesHasError = 1 << 19, // If this node or any of its children had an error - HasAggregatedChildData = 1 << 20, // If we've computed data from children and cached it in this node + ExportContext = 1 << 5, // Export context (initialized by binding) + ContainsThis = 1 << 6, // Interface contains references to "this" + HasImplicitReturn = 1 << 7, // If function implicitly returns on one of codepaths (initialized by binding) + HasExplicitReturn = 1 << 8, // If function has explicit reachable return on one of codepaths (initialized by binding) + GlobalAugmentation = 1 << 9, // Set if module declaration is an augmentation for the global scope + HasAsyncFunctions = 1 << 10, // If the file has async functions (initialized by binding) + DisallowInContext = 1 << 11, // If node was parsed in a context where 'in-expressions' are not allowed + YieldContext = 1 << 12, // If node was parsed in the 'yield' context created when parsing a generator + DecoratorContext = 1 << 13, // If node was parsed as part of a decorator + AwaitContext = 1 << 14, // If node was parsed in the 'await' context created when parsing an async function + ThisNodeHasError = 1 << 15, // If the parser encountered an error when parsing the code that created this node + JavaScriptFile = 1 << 16, // If node was parsed in a JavaScript + ThisNodeOrAnySubNodesHasError = 1 << 17, // If this node or any of its children had an error + HasAggregatedChildData = 1 << 18, // If we've computed data from children and cached it in this node // This flag will be set when the parser encounters a dynamic import expression so that module resolution // will not have to walk the tree if the flag is not set. However, this flag is just a approximation because @@ -453,8 +456,8 @@ namespace ts { // The advantage of this approach is its simplicity. For the case of batch compilation, // we guarantee that users won't have to pay the price of walking the tree if a dynamic import isn't used. /* @internal */ - PossiblyContainsDynamicImport = 1 << 20, - JSDoc = 1 << 21, // If node was parsed inside jsdoc + PossiblyContainsDynamicImport = 1 << 19, + JSDoc = 1 << 20, // If node was parsed inside jsdoc BlockScoped = Let | Const, @@ -462,12 +465,10 @@ namespace ts { ReachabilityAndEmitFlags = ReachabilityCheckFlags | HasAsyncFunctions, // Parsing context flags - ContextFlags = DisallowInContext | YieldContext | DecoratorContext | AwaitContext | JavaScriptFile | OptionalChain, + ContextFlags = DisallowInContext | YieldContext | DecoratorContext | AwaitContext | JavaScriptFile, // Exclude these flags when parsing a Type TypeExcludesFlags = YieldContext | AwaitContext, - - Optional = OptionalExpression | OptionalChain, } export const enum ModifierFlags { @@ -1567,6 +1568,36 @@ namespace ts { expression: ImportExpression; } + export interface OptionalExpression extends MemberExpression { + kind: SyntaxKind.OptionalExpression; + expression: LeftHandSideExpression; + chain: OptionalChain; + } + + export type OptionalChain = PropertyAccessChain | ElementAccessChain | CallChain; + + export interface PropertyAccessChain extends Node { + kind: SyntaxKind.PropertyAccessChain; + parent?: OptionalChain | OptionalExpression; + chain?: OptionalChain; + name: Identifier; + } + + export interface ElementAccessChain extends Node { + kind: SyntaxKind.ElementAccessChain; + chain?: OptionalChain; + parent?: OptionalChain | OptionalExpression; + argumentExpression: Expression; + } + + export interface CallChain extends Node { + kind: SyntaxKind.CallChain; + chain?: OptionalChain; + parent?: OptionalChain | OptionalExpression; + typeArguments?: NodeArray; + arguments: NodeArray; + } + export interface ExpressionWithTypeArguments extends TypeNode { kind: SyntaxKind.ExpressionWithTypeArguments; parent?: HeritageClause; @@ -1587,7 +1618,7 @@ namespace ts { template: TemplateLiteral; } - export type CallLikeExpression = CallExpression | NewExpression | TaggedTemplateExpression | Decorator | JsxOpeningLikeElement; + export type CallLikeExpression = CallExpression | CallChain | NewExpression | TaggedTemplateExpression | Decorator | JsxOpeningLikeElement; export interface AsExpression extends Expression { kind: SyntaxKind.AsExpression; @@ -3221,18 +3252,16 @@ namespace ts { NonPrimitive = 1 << 24, // intrinsic object type /* @internal */ JsxAttributes = 1 << 25, // Jsx attributes type - /* @internal */ - Optional = 1 << 26, // Optional chaining type marker Nullable = Undefined | Null, Literal = StringLiteral | NumberLiteral | BooleanLiteral, Unit = Literal | Nullable, StringOrNumberLiteral = StringLiteral | NumberLiteral, /* @internal */ - DefinitelyFalsy = StringLiteral | NumberLiteral | BooleanLiteral | Void | Undefined | Null | Optional, + DefinitelyFalsy = StringLiteral | NumberLiteral | BooleanLiteral | Void | Undefined | Null, PossiblyFalsy = DefinitelyFalsy | String | Number | Boolean, /* @internal */ - Intrinsic = Any | String | Number | Boolean | BooleanLiteral | ESSymbol | Void | Undefined | Null | Never | NonPrimitive | Optional, + Intrinsic = Any | String | Number | Boolean | BooleanLiteral | ESSymbol | Void | Undefined | Null | Never | NonPrimitive, /* @internal */ Primitive = String | Number | Boolean | Enum | EnumLiteral | ESSymbol | Void | Undefined | Null | Literal, StringLike = String | StringLiteral | Index, diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index cc7002ca059..1cbfdf393a7 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -4464,6 +4464,19 @@ namespace ts { return node.kind === SyntaxKind.MetaProperty; } + export function isOptionalExpression(node: Node): node is OptionalExpression { + return node.kind === SyntaxKind.OptionalExpression; + } + + // Optional chains + + export function isOptionalChain(node: Node): node is OptionalChain { + const kind = node.kind; + return kind === SyntaxKind.PropertyAccessChain + || kind === SyntaxKind.ElementAccessChain + || kind === SyntaxKind.CallChain; + } + // Misc export function isTemplateSpan(node: Node): node is TemplateSpan { @@ -5201,6 +5214,7 @@ namespace ts { case SyntaxKind.SuperKeyword: case SyntaxKind.NonNullExpression: case SyntaxKind.MetaProperty: + case SyntaxKind.OptionalExpression: case SyntaxKind.ImportKeyword: // technically this is only an Expression if it's in a CallExpression return true; default: diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index 7d46630e227..0e3c4a4a5a0 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -446,6 +446,11 @@ namespace ts { visitNode((node).expression, visitor, isExpression), visitNode((node).argumentExpression, visitor, isExpression)); + case SyntaxKind.OptionalExpression: + return updateOptionalExpression(node, + visitNode((node).expression, visitor, isExpression), + visitNode((node).chain, visitor, isOptionalChain)); + case SyntaxKind.CallExpression: return updateCall(node, visitNode((node).expression, visitor, isExpression), @@ -569,6 +574,24 @@ namespace ts { return updateMetaProperty(node, visitNode((node).name, visitor, isIdentifier)); + // Optional chains + + case SyntaxKind.PropertyAccessChain: + return updatePropertyAccessChain(node, + visitNode((node).chain, visitor, isOptionalChain), + visitNode((node).name, visitor, isIdentifier)); + + case SyntaxKind.ElementAccessChain: + return updateElementAccessChain(node, + visitNode((node).chain, visitor, isOptionalChain), + visitNode((node).argumentExpression, visitor, isExpression)); + + case SyntaxKind.CallChain: + return updateCallChain(node, + visitNode((node).chain, visitor, isOptionalChain), + nodesVisitor((node).typeArguments, visitor, isTypeNode), + nodesVisitor((node).arguments, visitor, isExpression)); + // Misc case SyntaxKind.TemplateSpan: @@ -1063,6 +1086,11 @@ namespace ts { result = reduceNode((node).argumentExpression, cbNode, result); break; + case SyntaxKind.OptionalExpression: + result = reduceNode((node).expression, cbNode, result); + result = reduceNode((node).chain, cbNode, result); + break; + case SyntaxKind.CallExpression: result = reduceNode((node).expression, cbNode, result); result = reduceNodes((node).typeArguments, cbNodes, result); @@ -1152,6 +1180,24 @@ namespace ts { result = reduceNode((node).type, cbNode, result); break; + // Optional chains + + case SyntaxKind.PropertyAccessChain: + result = reduceNode((node).chain, cbNode, result); + result = reduceNode((node).name, cbNode, result); + break; + + case SyntaxKind.ElementAccessChain: + result = reduceNode((node).chain, cbNode, result); + result = reduceNode((node).argumentExpression, cbNode, result); + break; + + case SyntaxKind.CallChain: + result = reduceNode((node).chain, cbNode, result); + result = reduceNodes((node).typeArguments, cbNode, result); + result = reduceNodes((node).arguments, cbNode, result); + break; + // Misc case SyntaxKind.TemplateSpan: result = reduceNode((node).expression, cbNode, result);