From c95daab0f497e7c285a27b03693efdee696ffb28 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Fri, 6 Sep 2019 13:23:50 -0700 Subject: [PATCH 01/19] Add support for Optional Chaining --- src/compiler/binder.ts | 12 + src/compiler/checker.ts | 124 +++- src/compiler/debug.ts | 8 + src/compiler/emitter.ts | 110 ++- src/compiler/factory.ts | 78 ++ src/compiler/parser.ts | 155 ++-- src/compiler/scanner.ts | 4 + src/compiler/transformers/esnext.ts | 153 ++++ src/compiler/types.ts | 83 ++- src/compiler/utilities.ts | 54 +- src/compiler/visitor.ts | 19 + src/services/completions.ts | 16 +- src/services/services.ts | 3 + src/services/textChanges.ts | 3 +- src/services/types.ts | 1 + src/services/utilities.ts | 17 +- .../reference/api/tsserverlibrary.d.ts | 699 +++++++++--------- tests/baselines/reference/api/typescript.d.ts | 699 +++++++++--------- tests/baselines/reference/callChain.js | 17 + tests/baselines/reference/callChain.symbols | 28 + tests/baselines/reference/callChain.types | 31 + .../baselines/reference/elementAccessChain.js | 21 + .../reference/elementAccessChain.symbols | 39 + .../reference/elementAccessChain.types | 48 ++ .../reference/propertyAccessChain.js | 17 + .../reference/propertyAccessChain.symbols | 34 + .../reference/propertyAccessChain.types | 34 + .../optionalChaining/callChain/callChain.ts | 10 + .../elementAccessChain/elementAccessChain.ts | 12 + .../propertyAccessChain.ts | 10 + .../fourslash/signatureHelpOptionalCall.ts | 14 + 31 files changed, 1737 insertions(+), 816 deletions(-) create mode 100644 tests/baselines/reference/callChain.js create mode 100644 tests/baselines/reference/callChain.symbols create mode 100644 tests/baselines/reference/callChain.types create mode 100644 tests/baselines/reference/elementAccessChain.js create mode 100644 tests/baselines/reference/elementAccessChain.symbols create mode 100644 tests/baselines/reference/elementAccessChain.types create mode 100644 tests/baselines/reference/propertyAccessChain.js create mode 100644 tests/baselines/reference/propertyAccessChain.symbols create mode 100644 tests/baselines/reference/propertyAccessChain.types create mode 100644 tests/cases/conformance/expressions/optionalChaining/callChain/callChain.ts create mode 100644 tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.ts create mode 100644 tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.ts create mode 100644 tests/cases/fourslash/signatureHelpOptionalCall.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index acc9db3e629f8..6f2b1efb19c61 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -3160,6 +3160,10 @@ namespace ts { const callee = skipOuterExpressions(node.expression); const expression = node.expression; + if (node.flags & NodeFlags.OptionalChain) { + transformFlags |= TransformFlags.ContainsESNext; + } + if (node.typeArguments) { transformFlags |= TransformFlags.AssertTypeScript; } @@ -3555,6 +3559,10 @@ namespace ts { function computePropertyAccess(node: PropertyAccessExpression, subtreeFlags: TransformFlags) { let transformFlags = subtreeFlags; + if (node.flags & NodeFlags.OptionalChain) { + transformFlags |= TransformFlags.ContainsESNext; + } + // If a PropertyAccessExpression starts with a super keyword, then it is // ES6 syntax, and requires a lexical `this` binding. if (node.expression.kind === SyntaxKind.SuperKeyword) { @@ -3570,6 +3578,10 @@ namespace ts { function computeElementAccess(node: ElementAccessExpression, subtreeFlags: TransformFlags) { let transformFlags = subtreeFlags; + if (node.flags & NodeFlags.OptionalChain) { + transformFlags |= TransformFlags.ContainsESNext; + } + // If an ElementAccessExpression starts with a super keyword, then it is // ES6 syntax, and requires a lexical `this` binding. if (node.expression.kind === SyntaxKind.SuperKeyword) { diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2328b7e3d68a4..5c81e13d92cad 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -383,6 +383,7 @@ namespace ts { getReturnTypeOfSignature, getNullableType, getNonNullableType, + getNonOptionalType: removeOptionalTypeMarker, typeToTypeNode: nodeBuilder.typeToTypeNode, indexInfoToIndexSignatureDeclaration: nodeBuilder.indexInfoToIndexSignatureDeclaration, signatureToSignatureDeclaration: nodeBuilder.signatureToSignatureDeclaration, @@ -542,6 +543,7 @@ namespace ts { getNullType: () => nullType, getESSymbolType: () => esSymbolType, getNeverType: () => neverType, + getOptionalType: () => optionalType, isSymbolAccessible, getObjectFlags, isArrayType, @@ -644,6 +646,7 @@ namespace ts { const unknownType = createIntrinsicType(TypeFlags.Unknown, "unknown"); const undefinedType = createIntrinsicType(TypeFlags.Undefined, "undefined"); const undefinedWideningType = strictNullChecks ? undefinedType : createIntrinsicType(TypeFlags.Undefined, "undefined", ObjectFlags.ContainsWideningType); + const optionalType = createIntrinsicType(TypeFlags.Undefined, "undefined"); const nullType = createIntrinsicType(TypeFlags.Null, "null"); const nullWideningType = strictNullChecks ? nullType : createIntrinsicType(TypeFlags.Null, "null", ObjectFlags.ContainsWideningType); const stringType = createIntrinsicType(TypeFlags.String, "string"); @@ -7157,6 +7160,12 @@ namespace ts { return result; } + function createOptionalCallSignature(signature: Signature) { + const result = cloneSignature(signature); + result.isOptionalCall = true; + return result; + } + function getExpandedParameters(sig: Signature): ReadonlyArray { if (sig.hasRestParameter) { const restIndex = sig.parameters.length - 1; @@ -8779,6 +8788,9 @@ 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 (!popTypeResolution()) { if (signature.declaration) { const typeNode = getEffectiveReturnTypeNode(signature.declaration); @@ -14034,7 +14046,7 @@ namespace ts { for (const sourceProp of excludeProperties(getPropertiesOfType(source), excludedProperties)) { if (!getPropertyOfObjectType(target, sourceProp.escapedName)) { const sourceType = getTypeOfSymbol(sourceProp); - if (!(sourceType === undefinedType || sourceType === undefinedWideningType)) { + if (!(sourceType === undefinedType || sourceType === undefinedWideningType || sourceType === optionalType)) { if (reportErrors) { reportError(Diagnostics.Property_0_does_not_exist_on_type_1, symbolToString(sourceProp), typeToString(target)); } @@ -14977,6 +14989,50 @@ namespace ts { return strictNullChecks ? getGlobalNonNullableTypeInstantiation(type) : type; } + function addOptionalTypeMarker(type: Type) { + return strictNullChecks ? getUnionType([type, optionalType]) : type; + } + + function removeOptionalTypeMarker(type: Type): Type { + return strictNullChecks ? filterType(type, t => t !== optionalType) : type; + } + + function propagateOptionalTypeMarker(type: Type, wasOptional: boolean) { + return wasOptional ? addOptionalTypeMarker(type) : type; + } + + 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. + if (isNullableType(type)) { + isOptional = true; + type = getNonNullableType(type); + } + return { isOptional, type }; + } + + // If we do not have a questionDotToken, then we are an OptionalChain and we remove the optionalType and + // add it back into the result, if needed. + const nonOptionalType = removeOptionalTypeMarker(type); + if (nonOptionalType !== type) { + isOptional = true; + type = nonOptionalType; + } + } + + type = checkNonNullType(type, expression, nullDiagnostic, undefinedDiagnostic, nullOrUndefinedDiagnostic); + return { isOptional, type }; + } /** * Is source potentially coercible to target type under `==`. @@ -20668,12 +20724,12 @@ namespace ts { ); } + function isNullableType(type: Type) { + return !!((strictNullChecks ? getFalsyFlags(type) : type.flags) & TypeFlags.Nullable); + } + function getNonNullableTypeIfNeeded(type: Type) { - const kind = (strictNullChecks ? getFalsyFlags(type) : type.flags) & TypeFlags.Nullable; - if (kind) { - return getNonNullableType(type); - } - return type; + return isNullableType(type) ? getNonNullableType(type) : type; } function checkNonNullType( @@ -20724,8 +20780,7 @@ namespace ts { } function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, right: Identifier) { - let propType: Type; - const leftType = checkNonNullExpression(left); + const { isOptional, type: leftType } = checkOptionalExpression(node, left); const parentSymbol = getNodeLinks(left).resolvedSymbol; const assignmentKind = getAssignmentTargetKind(node); const apparentType = getApparentType(assignmentKind !== AssignmentKind.None || isMethodAccessForCall(node) ? getWidenedType(leftType) : leftType); @@ -20739,6 +20794,8 @@ namespace ts { if (isIdentifier(left) && parentSymbol && !(prop && isConstEnumOrConstEnumOnlyModule(prop))) { markAliasReferenced(parentSymbol, node); } + + let propType: Type; if (!prop) { const indexInfo = assignmentKind === AssignmentKind.None || !isGenericObjectType(leftType) || isThisTypeParameter(leftType) ? getIndexInfoOfType(apparentType, IndexKind.String) : undefined; if (!(indexInfo && indexInfo.type)) { @@ -20777,7 +20834,7 @@ namespace ts { } propType = getConstraintForLocation(getTypeOfSymbol(prop), node); } - return getFlowTypeOfAccessExpression(node, prop, propType, right); + return propagateOptionalTypeMarker(getFlowTypeOfAccessExpression(node, prop, propType, right), isOptional); } function getFlowTypeOfAccessExpression(node: ElementAccessExpression | PropertyAccessExpression | QualifiedName, prop: Symbol | undefined, propType: Type, errorNode: Node) { @@ -21134,9 +21191,8 @@ namespace ts { } function checkIndexedAccess(node: ElementAccessExpression): Type { - const exprType = checkNonNullExpression(node.expression); + const { isOptional, type: exprType } = checkOptionalExpression(node, node.expression); const objectType = getAssignmentTargetKind(node) !== AssignmentKind.None || isMethodAccessForCall(node) ? getWidenedType(exprType) : exprType; - const indexExpression = node.argumentExpression; const indexType = checkExpression(indexExpression); @@ -21154,7 +21210,7 @@ namespace ts { AccessFlags.Writing | (isGenericObjectType(objectType) && !isThisTypeParameter(objectType) ? AccessFlags.NoIndexSignatures : 0) : AccessFlags.None; const indexedAccessType = getIndexedAccessTypeOrUndefined(objectType, effectiveIndexType, node, accessFlags) || errorType; - return checkIndexedAccessIndexType(getFlowTypeOfAccessExpression(node, indexedAccessType.symbol, indexedAccessType, indexExpression), node); + return propagateOptionalTypeMarker(checkIndexedAccessIndexType(getFlowTypeOfAccessExpression(node, indexedAccessType.symbol, indexedAccessType, indexExpression), node), isOptional); } function checkThatExpressionIsProperSymbolReference(expression: Expression, expressionType: Type, reportError: boolean): boolean { @@ -21237,7 +21293,7 @@ namespace ts { // interface B extends A { (x: 'foo'): string } // const b: B; // b('foo') // <- here overloads should be processed as [(x:'foo'): string, (x: string): void] - function reorderCandidates(signatures: ReadonlyArray, result: Signature[]): void { + function reorderCandidates(signatures: ReadonlyArray, result: Signature[], isOptionalCall: boolean): void { let lastParent: Node | undefined; let lastSymbol: Symbol | undefined; let cutoffIndex = 0; @@ -21279,7 +21335,7 @@ namespace ts { spliceIndex = index; } - result.splice(spliceIndex, 0, signature); + result.splice(spliceIndex, 0, isOptionalCall ? createOptionalCallSignature(signature) : signature); } } @@ -21932,7 +21988,7 @@ namespace ts { return createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments, Diagnostics.Expected_0_type_arguments_but_got_1, belowArgCount === -Infinity ? aboveArgCount : belowArgCount, argCount); } - function resolveCall(node: CallLikeExpression, signatures: ReadonlyArray, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode, fallbackError?: DiagnosticMessage): Signature { + function resolveCall(node: CallLikeExpression, signatures: ReadonlyArray, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode, isOptionalCall: boolean, fallbackError?: DiagnosticMessage): Signature { const isTaggedTemplate = node.kind === SyntaxKind.TaggedTemplateExpression; const isDecorator = node.kind === SyntaxKind.Decorator; const isJsxOpeningOrSelfClosingElement = isJsxOpeningLikeElement(node); @@ -21951,7 +22007,7 @@ namespace ts { const candidates = candidatesOutArray || []; // reorderCandidates fills up the candidates array directly - reorderCandidates(signatures, candidates); + reorderCandidates(signatures, candidates, isOptionalCall); if (!candidates.length) { if (reportErrors) { diagnostics.add(getDiagnosticForCallNode(node, Diagnostics.Call_target_does_not_contain_any_signatures)); @@ -22335,13 +22391,14 @@ namespace ts { const baseTypeNode = getEffectiveBaseTypeNode(getContainingClass(node)!); if (baseTypeNode) { const baseConstructors = getInstantiatedConstructorsForTypeArguments(superType, baseTypeNode.typeArguments, baseTypeNode); - return resolveCall(node, baseConstructors, candidatesOutArray, checkMode); + return resolveCall(node, baseConstructors, candidatesOutArray, checkMode, /*isOptional*/ false); } } return resolveUntypedCall(node); } - const funcType = checkNonNullExpression( + const { isOptional, type: funcType } = checkOptionalExpression( + node, node.expression, Diagnostics.Cannot_invoke_an_object_which_is_possibly_null, Diagnostics.Cannot_invoke_an_object_which_is_possibly_undefined, @@ -22351,8 +22408,8 @@ namespace ts { if (funcType === silentNeverType) { return silentNeverSignature; } - const apparentType = getApparentType(funcType); + const apparentType = getApparentType(funcType); if (apparentType === errorType) { // Another error has already been reported return resolveErrorCall(node); @@ -22416,7 +22473,8 @@ namespace ts { error(node, Diagnostics.Value_of_type_0_is_not_callable_Did_you_mean_to_include_new, typeToString(funcType)); return resolveErrorCall(node); } - return resolveCall(node, callSignatures, candidatesOutArray, checkMode); + + return resolveCall(node, callSignatures, candidatesOutArray, checkMode, isOptional); } function isGenericFunctionReturningFunction(signature: Signature) { @@ -22487,7 +22545,7 @@ namespace ts { return resolveErrorCall(node); } - return resolveCall(node, constructSignatures, candidatesOutArray, checkMode); + return resolveCall(node, constructSignatures, candidatesOutArray, checkMode, /*isOptional*/ false); } // If expressionType's apparent type is an object type with no construct signatures but @@ -22496,7 +22554,7 @@ namespace ts { // operation is Any. It is an error to have a Void this type. const callSignatures = getSignaturesOfType(expressionType, SignatureKind.Call); if (callSignatures.length) { - const signature = resolveCall(node, callSignatures, candidatesOutArray, checkMode); + const signature = resolveCall(node, callSignatures, candidatesOutArray, checkMode, /*isOptional*/ false); if (!noImplicitAny) { if (signature.declaration && !isJSConstructor(signature.declaration) && getReturnTypeOfSignature(signature) !== voidType) { error(node, Diagnostics.Only_a_void_function_can_be_called_with_the_new_keyword); @@ -22711,7 +22769,7 @@ namespace ts { return resolveErrorCall(node); } - return resolveCall(node, callSignatures, candidatesOutArray, checkMode); + return resolveCall(node, callSignatures, candidatesOutArray, checkMode, /*isOptional*/ false); } /** @@ -22774,7 +22832,7 @@ namespace ts { return resolveErrorCall(node); } - return resolveCall(node, callSignatures, candidatesOutArray, checkMode, headMessage); + return resolveCall(node, callSignatures, candidatesOutArray, checkMode, /*isOptional*/ false, headMessage); } function createSignatureForJSXIntrinsic(node: JsxOpeningLikeElement, result: Type): Signature { @@ -22827,7 +22885,7 @@ namespace ts { return resolveErrorCall(node); } - return resolveCall(node, signatures, candidatesOutArray, checkMode); + return resolveCall(node, signatures, candidatesOutArray, checkMode, /*isOptional*/ false); } /** @@ -23004,18 +23062,20 @@ namespace ts { if (returnType.flags & TypeFlags.ESSymbolLike && isSymbolOrSymbolForCall(node)) { return getESSymbolLikeTypeForNode(walkUpParenthesizedExpressions(node.parent)); } - let jsAssignmentType: Type | undefined; + if (isInJSFile(node)) { const decl = getDeclarationOfExpando(node); if (decl) { const jsSymbol = getSymbolOfNode(decl); if (jsSymbol && hasEntries(jsSymbol.exports)) { - jsAssignmentType = createAnonymousType(jsSymbol, jsSymbol.exports, emptyArray, emptyArray, undefined, undefined); - (jsAssignmentType as ObjectType).objectFlags |= ObjectFlags.JSLiteral; + const jsAssignmentType = createAnonymousType(jsSymbol, jsSymbol.exports, emptyArray, emptyArray, undefined, undefined); + jsAssignmentType.objectFlags |= ObjectFlags.JSLiteral; + return getIntersectionType([returnType, jsAssignmentType]); } } } - return jsAssignmentType ? getIntersectionType([returnType, jsAssignmentType]) : returnType; + + return returnType; } function isSymbolOrSymbolForCall(node: Node) { @@ -25270,11 +25330,11 @@ namespace ts { const expr = skipParentheses(node); // 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 (expr.kind === SyntaxKind.CallExpression && (expr).expression.kind !== SyntaxKind.SuperKeyword && !isRequireCall(expr, /*checkArgumentIsStringLiteralLike*/ true) && !isSymbolOrSymbolForCall(expr)) { - const funcType = checkNonNullExpression((expr).expression); + if (isCallExpression(expr) && expr.expression.kind !== SyntaxKind.SuperKeyword && !isRequireCall(expr, /*checkArgumentIsStringLiteralLike*/ true) && !isSymbolOrSymbolForCall(expr)) { + const { isOptional, type: funcType } = checkOptionalExpression(expr, expr.expression); const signature = getSingleCallSignature(funcType); if (signature && !signature.typeParameters) { - return getReturnTypeOfSignature(signature); + return propagateOptionalTypeMarker(getReturnTypeOfSignature(signature), isOptional); } } else if (isAssertionExpression(expr) && !isConstTypeReference(expr.type)) { diff --git a/src/compiler/debug.ts b/src/compiler/debug.ts index c90f4d1fb01f5..933e649d72d64 100644 --- a/src/compiler/debug.ts +++ b/src/compiler/debug.ts @@ -184,6 +184,14 @@ namespace ts { assertNode) : noop; + export const assertNotNode = shouldAssert(AssertionLevel.Normal) + ? (node: Node | undefined, test: ((node: Node | undefined) => boolean) | undefined, message?: string): void => assert( + test === undefined || !test(node), + message || "Unexpected node.", + () => `Node ${formatSyntaxKind(node!.kind)} should not have passed test '${getFunctionName(test!)}'.`, + assertNode) + : noop; + export const assertOptionalNode = shouldAssert(AssertionLevel.Normal) ? (node: Node, test: (node: Node) => boolean, message?: string): void => assert( test === undefined || node === undefined || test(node), diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 57cc47cf0e12a..611d4565f0fff 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -821,6 +821,8 @@ namespace ts { let detachedCommentsInfo: { nodePos: number, detachedCommentEndPos: number}[] | undefined; let hasWrittenComment = false; let commentsDisabled = !!printerOptions.removeComments; + let lastNode: Node | undefined; + let lastSubstitution: Node | undefined; const { enter: enterComment, exit: exitComment } = performance.createTimerIf(extendedDiagnostics, "commentTime", "beforeComment", "afterComment"); reset(); @@ -1043,8 +1045,7 @@ namespace ts { setSourceFile(sourceFile); } - const pipelinePhase = getPipelinePhase(PipelinePhase.Notification, node); - pipelinePhase(hint, node); + pipelineEmit(hint, node); } function setSourceFile(sourceFile: SourceFile | undefined) { @@ -1076,6 +1077,8 @@ namespace ts { currentSourceFile = undefined!; currentLineMap = undefined!; detachedCommentsInfo = undefined; + lastNode = undefined; + lastSubstitution = undefined; setWriter(/*output*/ undefined, /*_sourceMapGenerator*/ undefined); } @@ -1083,24 +1086,47 @@ namespace ts { return currentLineMap || (currentLineMap = getLineStarts(currentSourceFile!)); } + function emit(node: Node): Node; + function emit(node: Node | undefined): Node | undefined; function emit(node: Node | undefined) { if (node === undefined) return; + const prevSourceFileTextKind = recordBundleFileInternalSectionStart(node); - const pipelinePhase = getPipelinePhase(PipelinePhase.Notification, node); - pipelinePhase(EmitHint.Unspecified, node); + const substitute = pipelineEmit(EmitHint.Unspecified, node); recordBundleFileInternalSectionEnd(prevSourceFileTextKind); + return substitute; } - function emitIdentifierName(node: Identifier | undefined) { + function emitIdentifierName(node: Identifier): Node; + function emitIdentifierName(node: Identifier | undefined): Node | undefined; + function emitIdentifierName(node: Identifier | undefined): Node | undefined { if (node === undefined) return; - const pipelinePhase = getPipelinePhase(PipelinePhase.Notification, node); - pipelinePhase(EmitHint.IdentifierName, node); + return pipelineEmit(EmitHint.IdentifierName, node); } - function emitExpression(node: Expression | undefined) { + function emitExpression(node: Expression): Node; + function emitExpression(node: Expression | undefined): Node | undefined; + function emitExpression(node: Expression | undefined): Node | undefined { if (node === undefined) return; + return pipelineEmit(EmitHint.Expression, node); + } + + function pipelineEmit(emitHint: EmitHint, node: Node) { + const savedLastNode = lastNode; + const savedLastSubstitution = lastSubstitution; + lastNode = node; + lastSubstitution = undefined; + const pipelinePhase = getPipelinePhase(PipelinePhase.Notification, node); - pipelinePhase(EmitHint.Expression, node); + pipelinePhase(emitHint, node); + + Debug.assert(lastNode === node); + + const substitute = lastSubstitution; + lastNode = savedLastNode; + lastSubstitution = savedLastSubstitution; + + return substitute || node; } function getPipelinePhase(phase: PipelinePhase, node: Node) { @@ -1142,11 +1168,14 @@ namespace ts { } function pipelineEmitWithNotification(hint: EmitHint, node: Node) { + Debug.assert(lastNode === node); const pipelinePhase = getNextPipelinePhase(PipelinePhase.Notification, node); onEmitNode(hint, node, pipelinePhase); + Debug.assert(lastNode === node); } function pipelineEmitWithHint(hint: EmitHint, node: Node): void { + Debug.assert(lastNode === node || lastSubstitution === node); if (hint === EmitHint.SourceFile) return emitSourceFile(cast(node, isSourceFile)); if (hint === EmitHint.IdentifierName) return emitIdentifier(cast(node, isIdentifier)); if (hint === EmitHint.MappedTypeParameter) return emitMappedTypeParameter(cast(node, isTypeParameterDeclaration)); @@ -1459,7 +1488,7 @@ namespace ts { if (isExpression(node)) { hint = EmitHint.Expression; if (substituteNode !== noEmitSubstitution) { - node = substituteNode(hint, node); + lastSubstitution = node = substituteNode(hint, node); } } else if (isToken(node)) { @@ -1575,8 +1604,11 @@ namespace ts { } function pipelineEmitWithSubstitution(hint: EmitHint, node: Node) { + Debug.assert(lastNode === node || lastSubstitution === node); const pipelinePhase = getNextPipelinePhase(PipelinePhase.Substitution, node); - pipelinePhase(hint, substituteNode(hint, node)); + lastSubstitution = substituteNode(hint, node); + pipelinePhase(hint, lastSubstitution); + Debug.assert(lastNode === node || lastSubstitution === node); } function getHelpersFromBundledSourceFiles(bundle: Bundle): string[] | undefined { @@ -2074,8 +2106,7 @@ namespace ts { } writePunctuation("["); - const pipelinePhase = getPipelinePhase(PipelinePhase.Notification, node.typeParameter); - pipelinePhase(EmitHint.MappedTypeParameter, node.typeParameter); + pipelineEmit(EmitHint.MappedTypeParameter, node.typeParameter); writePunctuation("]"); if (node.questionToken) { @@ -2173,34 +2204,24 @@ namespace ts { } function emitPropertyAccessExpression(node: PropertyAccessExpression) { - let indentBeforeDot = false; - let indentAfterDot = false; - const dotRangeFirstCommentStart = skipTrivia( - currentSourceFile!.text, - node.expression.end, - /*stopAfterLineBreak*/ false, - /*stopAtComments*/ true - ); - const dotRangeStart = skipTrivia(currentSourceFile!.text, dotRangeFirstCommentStart); - const dotRangeEnd = dotRangeStart + 1; - if (!(getEmitFlags(node) & EmitFlags.NoIndentation)) { - const dotToken = createToken(SyntaxKind.DotToken); - dotToken.pos = node.expression.end; - dotToken.end = dotRangeEnd; - indentBeforeDot = needsIndentation(node, node.expression, dotToken); - indentAfterDot = needsIndentation(node, dotToken, node.name); - } + const expression = cast(emitExpression(node.expression), isExpression); + const token = getDotOrQuestionDotToken(node); + const indentBeforeDot = needsIndentation(node, node.expression, token); + const indentAfterDot = needsIndentation(node, token, node.name); - emitExpression(node.expression); increaseIndentIf(indentBeforeDot, /*writeSpaceIfNotIndenting*/ false); - const dotHasCommentTrivia = dotRangeFirstCommentStart !== dotRangeStart; - const shouldEmitDotDot = !indentBeforeDot && needsDotDotForPropertyAccess(node.expression, dotHasCommentTrivia); + const shouldEmitDotDot = + token.kind !== SyntaxKind.QuestionDotToken && + mayNeedDotDotForPropertyAccess(expression) && + !writer.hasPrecedingComment() && + !writer.hasPrecedingWhitespace(); + if (shouldEmitDotDot) { writePunctuation("."); } - emitTokenWithComment(SyntaxKind.DotToken, node.expression.end, writePunctuation, node); + emitTokenWithComment(token.kind, node.expression.end, writePunctuation, node); increaseIndentIf(indentAfterDot, /*writeSpaceIfNotIndenting*/ false); emit(node.name); decreaseIndentIf(indentBeforeDot, indentAfterDot); @@ -2208,28 +2229,27 @@ namespace ts { // 1..toString is a valid property access, emit a dot after the literal // Also emit a dot if expression is a integer const enum value - it will appear in generated code as numeric literal - function needsDotDotForPropertyAccess(expression: Expression, dotHasTrivia: boolean) { + function mayNeedDotDotForPropertyAccess(expression: Expression) { expression = skipPartiallyEmittedExpressions(expression); if (isNumericLiteral(expression)) { // check if numeric literal is a decimal literal that was originally written with a dot const text = getLiteralTextOfNode(expression, /*neverAsciiEscape*/ true); // If he number will be printed verbatim and it doesn't already contain a dot, add one // if the expression doesn't have any comments that will be emitted. - return !expression.numericLiteralFlags && !stringContains(text, tokenToString(SyntaxKind.DotToken)!) && - (!dotHasTrivia || printerOptions.removeComments); + return !expression.numericLiteralFlags && !stringContains(text, tokenToString(SyntaxKind.DotToken)!); } else if (isPropertyAccessExpression(expression) || isElementAccessExpression(expression)) { // check if constant enum value is integer const constantValue = getConstantValue(expression); // isFinite handles cases when constantValue is undefined return typeof constantValue === "number" && isFinite(constantValue) - && Math.floor(constantValue) === constantValue - && printerOptions.removeComments; + && Math.floor(constantValue) === constantValue; } } function emitElementAccessExpression(node: ElementAccessExpression) { emitExpression(node.expression); + emit(node.questionDotToken); emitTokenWithComment(SyntaxKind.OpenBracketToken, node.expression.end, writePunctuation, node); emitExpression(node.argumentExpression); emitTokenWithComment(SyntaxKind.CloseBracketToken, node.argumentExpression.end, writePunctuation, node); @@ -2237,6 +2257,7 @@ namespace ts { function emitCallExpression(node: CallExpression) { emitExpression(node.expression); + emit(node.questionDotToken); emitTypeArguments(node, node.typeArguments); emitExpressionList(node, node.arguments, ListFormat.CallExpressionArguments); } @@ -3700,8 +3721,7 @@ namespace ts { writeLine(); increaseIndent(); if (isEmptyStatement(node)) { - const pipelinePhase = getPipelinePhase(PipelinePhase.Notification, node); - pipelinePhase(EmitHint.EmbeddedStatement, node); + pipelineEmit(EmitHint.EmbeddedStatement, node); } else { emit(node); @@ -4172,6 +4192,10 @@ namespace ts { } function needsIndentation(parent: Node, node1: Node, node2: Node): boolean { + if (getEmitFlags(parent) & EmitFlags.NoIndentation) { + return false; + } + parent = skipSynthesizedParentheses(parent); node1 = skipSynthesizedParentheses(node1); node2 = skipSynthesizedParentheses(node2); @@ -4627,6 +4651,7 @@ namespace ts { // Comments function pipelineEmitWithComments(hint: EmitHint, node: Node) { + Debug.assert(lastNode === node || lastSubstitution === node); enterComment(); hasWrittenComment = false; const emitFlags = getEmitFlags(node); @@ -4693,6 +4718,7 @@ namespace ts { } } exitComment(); + Debug.assert(lastNode === node || lastSubstitution === node); } function emitLeadingSynthesizedComment(comment: SynthesizedComment) { @@ -4939,6 +4965,7 @@ namespace ts { } function pipelineEmitWithSourceMap(hint: EmitHint, node: Node) { + Debug.assert(lastNode === node || lastSubstitution === node); const pipelinePhase = getNextPipelinePhase(PipelinePhase.SourceMaps, node); if (isUnparsedSource(node) || isUnparsedPrepend(node)) { pipelinePhase(hint, node); @@ -4981,6 +5008,7 @@ namespace ts { emitSourcePos(source, end); } } + Debug.assert(lastNode === node || lastSubstitution === node); } /** diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index d0d617b349531..ea35b30eed44d 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -1013,6 +1013,7 @@ namespace ts { } export function updatePropertyAccess(node: PropertyAccessExpression, expression: Expression, name: Identifier) { + Debug.assert(!(node.flags & NodeFlags.OptionalChain), "Cannot update a PropertyAccessChain using updatePropertyAccess. Use updatePropertyAccessChain instead."); // Because we are updating existed propertyAccess we want to inherit its emitFlags // instead of using the default from createPropertyAccess return node.expression !== expression @@ -1021,6 +1022,27 @@ namespace ts { : node; } + export function createPropertyAccessChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, name: string | Identifier) { + const node = createSynthesizedNode(SyntaxKind.PropertyAccessExpression); + node.flags |= NodeFlags.OptionalChain; + node.expression = parenthesizeForAccess(expression); + node.questionDotToken = questionDotToken; + node.name = asName(name); + setEmitFlags(node, EmitFlags.NoIndentation); + return node; + } + + export function updatePropertyAccessChain(node: PropertyAccessChain, expression: Expression, questionDotToken: QuestionDotToken | undefined, name: Identifier) { + Debug.assert(!!(node.flags & NodeFlags.OptionalChain), "Cannot update a PropertyAccessExpression using updatePropertyAccessChain. Use updatePropertyAccess instead."); + // Because we are updating an existing PropertyAccessChain we want to inherit its emitFlags + // instead of using the default from createPropertyAccess + return node.expression !== expression + || node.questionDotToken !== questionDotToken + || node.name !== name + ? updateNode(setEmitFlags(createPropertyAccessChain(expression, questionDotToken, name), getEmitFlags(node)), node) + : node; + } + export function createElementAccess(expression: Expression, index: number | Expression) { const node = createSynthesizedNode(SyntaxKind.ElementAccessExpression); node.expression = parenthesizeForAccess(expression); @@ -1029,12 +1051,31 @@ namespace ts { } export function updateElementAccess(node: ElementAccessExpression, expression: Expression, argumentExpression: Expression) { + Debug.assert(!(node.flags & NodeFlags.OptionalChain), "Cannot update an ElementAccessChain using updateElementAccess. Use updateElementAccessChain instead."); return node.expression !== expression || node.argumentExpression !== argumentExpression ? updateNode(createElementAccess(expression, argumentExpression), node) : node; } + export function createElementAccessChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, index: number | Expression) { + const node = createSynthesizedNode(SyntaxKind.ElementAccessExpression); + node.flags |= NodeFlags.OptionalChain; + node.expression = parenthesizeForAccess(expression); + node.questionDotToken = questionDotToken; + node.argumentExpression = asExpression(index); + return node; + } + + export function updateElementAccessChain(node: ElementAccessChain, expression: Expression, questionDotToken: QuestionDotToken | undefined, argumentExpression: Expression) { + Debug.assert(!!(node.flags & NodeFlags.OptionalChain), "Cannot update an ElementAccessExpression using updateElementAccessChain. Use updateElementAccess instead."); + return node.expression !== expression + || node.questionDotToken !== questionDotToken + || node.argumentExpression !== argumentExpression + ? updateNode(createElementAccessChain(expression, questionDotToken, argumentExpression), node) + : node; + } + export function createCall(expression: Expression, typeArguments: ReadonlyArray | undefined, argumentsArray: ReadonlyArray | undefined) { const node = createSynthesizedNode(SyntaxKind.CallExpression); node.expression = parenthesizeForAccess(expression); @@ -1044,6 +1085,7 @@ namespace ts { } export function updateCall(node: CallExpression, expression: Expression, typeArguments: ReadonlyArray | undefined, argumentsArray: ReadonlyArray) { + Debug.assert(!(node.flags & NodeFlags.OptionalChain), "Cannot update a CallChain using updateCall. Use updateCallChain instead."); return node.expression !== expression || node.typeArguments !== typeArguments || node.arguments !== argumentsArray @@ -1051,6 +1093,26 @@ namespace ts { : node; } + export function createCallChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, typeArguments: ReadonlyArray | undefined, argumentsArray: ReadonlyArray | undefined) { + const node = createSynthesizedNode(SyntaxKind.CallExpression); + node.flags |= NodeFlags.OptionalChain; + node.expression = parenthesizeForAccess(expression); + node.questionDotToken = questionDotToken; + node.typeArguments = asNodeArray(typeArguments); + node.arguments = parenthesizeListElements(createNodeArray(argumentsArray)); + return node; + } + + export function updateCallChain(node: CallChain, expression: Expression, questionDotToken: QuestionDotToken | undefined, typeArguments: ReadonlyArray | undefined, argumentsArray: ReadonlyArray) { + Debug.assert(!!(node.flags & NodeFlags.OptionalChain), "Cannot update a CallExpression using updateCallChain. Use updateCall instead."); + return node.expression !== expression + || node.questionDotToken !== questionDotToken + || node.typeArguments !== typeArguments + || node.arguments !== argumentsArray + ? updateNode(createCallChain(expression, questionDotToken, typeArguments, argumentsArray), node) + : node; + } + export function createNew(expression: Expression, typeArguments: ReadonlyArray | undefined, argumentsArray: ReadonlyArray | undefined) { const node = createSynthesizedNode(SyntaxKind.NewExpression); node.expression = parenthesizeForNew(expression); @@ -2721,6 +2783,22 @@ namespace ts { : node; } + /* @internal */ + export function createSyntheticReferenceExpression(expression: Expression, thisArg: Expression) { + const node = createSynthesizedNode(SyntaxKind.SyntheticReferenceExpression); + node.expression = expression; + node.thisArg = thisArg; + return node; + } + + /* @internal */ + export function updateSyntheticReferenceExpression(node: SyntheticReferenceExpression, expression: Expression, thisArg: Expression) { + return node.expression !== expression + || node.thisArg !== thisArg + ? updateNode(createSyntheticReferenceExpression(expression, thisArg), node) + : node; + } + export function createBundle(sourceFiles: ReadonlyArray, prepends: ReadonlyArray = emptyArray) { const node = createNode(SyntaxKind.Bundle); node.prepends = prepends; diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index f1221883bde8c..d427de81dd9f4 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -213,17 +213,21 @@ namespace ts { return visitNodes(cbNode, cbNodes, (node).properties); case SyntaxKind.PropertyAccessExpression: return visitNode(cbNode, (node).expression) || + visitNode(cbNode, (node).questionDotToken) || visitNode(cbNode, (node).name); case SyntaxKind.ElementAccessExpression: return visitNode(cbNode, (node).expression) || + visitNode(cbNode, (node).questionDotToken) || visitNode(cbNode, (node).argumentExpression); case SyntaxKind.CallExpression: case SyntaxKind.NewExpression: return visitNode(cbNode, (node).expression) || + visitNode(cbNode, (node).questionDotToken) || visitNodes(cbNode, cbNodes, (node).typeArguments) || visitNodes(cbNode, cbNodes, (node).arguments); case SyntaxKind.TaggedTemplateExpression: return visitNode(cbNode, (node).tag) || + visitNode(cbNode, (node).questionDotToken) || visitNodes(cbNode, cbNodes, (node).typeArguments) || visitNode(cbNode, (node).template); case SyntaxKind.TypeAssertionExpression: @@ -4226,7 +4230,8 @@ 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. + // CallExpression or OptionalExpression. As such, we need to consume the rest + // of it here to be complete. return parseCallExpressionRest(expression); } @@ -4279,7 +4284,7 @@ namespace ts { // Because CallExpression and MemberExpression are left recursive, we need to bottom out // of the recursion immediately. So we parse out a primary expression to start with. const expression = parsePrimaryExpression(); - return parseMemberExpressionRest(expression); + return parseMemberExpressionRest(expression, /*allowOptionalChain*/ true); } function parseSuperExpression(): MemberExpression { @@ -4573,18 +4578,70 @@ namespace ts { return finishNode(node); } - function parseMemberExpressionRest(expression: LeftHandSideExpression): MemberExpression { + function nextTokenIsIdentifierOrKeywordOrOpenBracketOrTemplate() { + nextToken(); + return tokenIsIdentifierOrKeyword(token()) + || token() === SyntaxKind.OpenBracketToken + || isTemplateStartOfTaggedTemplate(); + } + + function isStartOfOptionalPropertyOrElementAccessChain() { + return token() === SyntaxKind.QuestionDotToken + && lookAhead(nextTokenIsIdentifierOrKeywordOrOpenBracketOrTemplate); + } + + function parsePropertyAccessExpressionRest(expression: LeftHandSideExpression, questionDotToken: QuestionDotToken | undefined) { + const propertyAccess = createNode(SyntaxKind.PropertyAccessExpression, expression.pos); + propertyAccess.expression = expression; + propertyAccess.questionDotToken = questionDotToken; + propertyAccess.name = parseRightSideOfDot(/*allowIdentifierNames*/ true); + if (questionDotToken || expression.flags & NodeFlags.OptionalChain) { + propertyAccess.flags |= NodeFlags.OptionalChain; + } + return finishNode(propertyAccess); + } + + function parseElementAccessExpressionRest(expression: LeftHandSideExpression, questionDotToken: QuestionDotToken | undefined) { + const indexedAccess = createNode(SyntaxKind.ElementAccessExpression, expression.pos); + indexedAccess.expression = expression; + indexedAccess.questionDotToken = questionDotToken; + + if (token() === SyntaxKind.CloseBracketToken) { + indexedAccess.argumentExpression = createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ true, Diagnostics.An_element_access_expression_should_take_an_argument); + } + else { + const argument = allowInAnd(parseExpression); + if (isStringOrNumericLiteralLike(argument)) { + argument.text = internIdentifier(argument.text); + } + indexedAccess.argumentExpression = argument; + } + + parseExpected(SyntaxKind.CloseBracketToken); + if (questionDotToken || expression.flags & NodeFlags.OptionalChain) { + indexedAccess.flags |= NodeFlags.OptionalChain; + } + return finishNode(indexedAccess); + } + + function parseMemberExpressionRest(expression: LeftHandSideExpression, allowOptionalChain: boolean): MemberExpression { while (true) { - const dotToken = parseOptionalToken(SyntaxKind.DotToken); - if (dotToken) { - const propertyAccess = createNode(SyntaxKind.PropertyAccessExpression, expression.pos); - propertyAccess.expression = expression; - propertyAccess.name = parseRightSideOfDot(/*allowIdentifierNames*/ true); - expression = finishNode(propertyAccess); + let questionDotToken: QuestionDotToken | undefined; + let isPropertyAccess = false; + if (allowOptionalChain && isStartOfOptionalPropertyOrElementAccessChain()) { + questionDotToken = parseExpectedToken(SyntaxKind.QuestionDotToken); + isPropertyAccess = token() !== SyntaxKind.OpenBracketToken; + } + else { + isPropertyAccess = parseOptional(SyntaxKind.DotToken); + } + + if (isPropertyAccess) { + expression = parsePropertyAccessExpressionRest(expression, questionDotToken); continue; } - if (token() === SyntaxKind.ExclamationToken && !scanner.hasPrecedingLineBreak()) { + if (!questionDotToken && token() === SyntaxKind.ExclamationToken && !scanner.hasPrecedingLineBreak()) { nextToken(); const nonNullExpression = createNode(SyntaxKind.NonNullExpression, expression.pos); nonNullExpression.expression = expression; @@ -4593,28 +4650,13 @@ namespace ts { } // when in the [Decorator] context, we do not parse ElementAccess as it could be part of a ComputedPropertyName - if (!inDecoratorContext() && parseOptional(SyntaxKind.OpenBracketToken)) { - const indexedAccess = createNode(SyntaxKind.ElementAccessExpression, expression.pos); - indexedAccess.expression = expression; - - if (token() === SyntaxKind.CloseBracketToken) { - indexedAccess.argumentExpression = createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ true, Diagnostics.An_element_access_expression_should_take_an_argument); - } - else { - const argument = allowInAnd(parseExpression); - if (isStringOrNumericLiteralLike(argument)) { - argument.text = internIdentifier(argument.text); - } - indexedAccess.argumentExpression = argument; - } - - parseExpected(SyntaxKind.CloseBracketToken); - expression = finishNode(indexedAccess); + if ((questionDotToken || !inDecoratorContext()) && parseOptional(SyntaxKind.OpenBracketToken)) { + expression = parseElementAccessExpressionRest(expression, questionDotToken); continue; } if (isTemplateStartOfTaggedTemplate()) { - expression = parseTaggedTemplateRest(expression, /*typeArguments*/ undefined); + expression = parseTaggedTemplateRest(expression, questionDotToken, /*typeArguments*/ undefined); continue; } @@ -4626,19 +4668,25 @@ namespace ts { return token() === SyntaxKind.NoSubstitutionTemplateLiteral || token() === SyntaxKind.TemplateHead; } - function parseTaggedTemplateRest(tag: LeftHandSideExpression, typeArguments: NodeArray | undefined) { + function parseTaggedTemplateRest(tag: LeftHandSideExpression, questionDotToken: QuestionDotToken | undefined, typeArguments: NodeArray | undefined) { const tagExpression = createNode(SyntaxKind.TaggedTemplateExpression, tag.pos); tagExpression.tag = tag; + tagExpression.questionDotToken = questionDotToken; tagExpression.typeArguments = typeArguments; tagExpression.template = token() === SyntaxKind.NoSubstitutionTemplateLiteral ? parseLiteralNode() : parseTemplateExpression(); + if (questionDotToken || tag.flags & NodeFlags.OptionalChain) { + tagExpression.flags |= NodeFlags.OptionalChain; + } return finishNode(tagExpression); } function parseCallExpressionRest(expression: LeftHandSideExpression): LeftHandSideExpression { while (true) { - expression = parseMemberExpressionRest(expression); + expression = parseMemberExpressionRest(expression, /*allowOptionalChain*/ true); + const questionDotToken = parseOptionalToken(SyntaxKind.QuestionDotToken); + // handle 'foo<()' if (token() === SyntaxKind.LessThanToken || token() === SyntaxKind.LessThanLessThanToken) { // See if this is the start of a generic invocation. If so, consume it and @@ -4646,32 +4694,47 @@ namespace ts { // part of an arithmetic expression. Break out so we consume it higher in the // stack. const typeArguments = tryParse(parseTypeArgumentsInExpression); - if (!typeArguments) { - return expression; - } + if (typeArguments) { + if (isTemplateStartOfTaggedTemplate()) { + expression = parseTaggedTemplateRest(expression, questionDotToken, typeArguments); + continue; + } - if (isTemplateStartOfTaggedTemplate()) { - expression = parseTaggedTemplateRest(expression, typeArguments); + const callExpr = createNode(SyntaxKind.CallExpression, expression.pos); + callExpr.expression = expression; + callExpr.questionDotToken = questionDotToken; + callExpr.typeArguments = typeArguments; + callExpr.arguments = parseArgumentList(); + if (questionDotToken || expression.flags & NodeFlags.OptionalChain) { + callExpr.flags |= NodeFlags.OptionalChain; + } + expression = finishNode(callExpr); continue; } - - const callExpr = createNode(SyntaxKind.CallExpression, expression.pos); - callExpr.expression = expression; - callExpr.typeArguments = typeArguments; - callExpr.arguments = parseArgumentList(); - expression = finishNode(callExpr); - continue; } else if (token() === SyntaxKind.OpenParenToken) { const callExpr = createNode(SyntaxKind.CallExpression, expression.pos); callExpr.expression = expression; + callExpr.questionDotToken = questionDotToken; callExpr.arguments = parseArgumentList(); + if (questionDotToken || expression.flags & NodeFlags.OptionalChain) { + callExpr.flags |= NodeFlags.OptionalChain; + } expression = finishNode(callExpr); continue; } - - return expression; + if (questionDotToken) { + // We failed to parse anything, so report a missing identifier here. + const propertyAccess = createNode(SyntaxKind.PropertyAccessExpression, expression.pos) as PropertyAccessExpression; + propertyAccess.expression = expression; + propertyAccess.questionDotToken = questionDotToken; + propertyAccess.name = createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ false); + propertyAccess.flags |= NodeFlags.OptionalChain; + expression = finishNode(propertyAccess); + } + break; } + return expression; } function parseArgumentList() { @@ -4940,12 +5003,12 @@ namespace ts { let expression: MemberExpression = parsePrimaryExpression(); let typeArguments; while (true) { - expression = parseMemberExpressionRest(expression); + expression = parseMemberExpressionRest(expression, /*allowOptionalChain*/ false); typeArguments = tryParse(parseTypeArgumentsInExpression); if (isTemplateStartOfTaggedTemplate()) { Debug.assert(!!typeArguments, "Expected a type argument list; all plain tagged template starts should be consumed in 'parseMemberExpressionRest'"); - expression = parseTaggedTemplateRest(expression, typeArguments); + expression = parseTaggedTemplateRest(expression, /*optionalChain*/ undefined, typeArguments); typeArguments = undefined; } break; diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index af9f565fb52ff..9ea9353a66086 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -183,6 +183,7 @@ namespace ts { "&&": SyntaxKind.AmpersandAmpersandToken, "||": SyntaxKind.BarBarToken, "?": SyntaxKind.QuestionToken, + "?.": SyntaxKind.QuestionDotToken, ":": SyntaxKind.ColonToken, "=": SyntaxKind.EqualsToken, "+=": SyntaxKind.PlusEqualsToken, @@ -1826,6 +1827,9 @@ namespace ts { pos++; return token = SyntaxKind.GreaterThanToken; case CharacterCodes.question: + if (text.charCodeAt(pos + 1) === CharacterCodes.dot && !isDigit(text.charCodeAt(pos + 2))) { + return pos += 2, token = SyntaxKind.QuestionDotToken; + } pos++; return token = SyntaxKind.QuestionToken; case CharacterCodes.openBracket: diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index 80debb46f8a04..c2693fb1a4aaf 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -1,6 +1,10 @@ /*@internal*/ namespace ts { export function transformESNext(context: TransformationContext) { + const { + hoistVariableDeclaration + } = context; + return chainBundle(transformSourceFile); function transformSourceFile(node: SourceFile) { @@ -16,9 +20,158 @@ namespace ts { return node; } switch (node.kind) { + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + case SyntaxKind.CallExpression: + case SyntaxKind.TaggedTemplateExpression: + if (node.flags & NodeFlags.OptionalChain) { + const updated = visitOptionalExpression(node as OptionalChain, /*captureThisArg*/ false); + Debug.assertNotNode(updated, isSyntheticReference); + return updated; + } + // falls through default: return visitEachChild(node, visitor, context); } } + + function flattenChain(chain: OptionalChain) { + const links: OptionalChain[] = [chain]; + while (!chain.questionDotToken && !isTaggedTemplateExpression(chain)) { + chain = cast(chain.expression, isOptionalChain); + links.unshift(chain); + } + return { expression: chain.expression, chain: links }; + } + + function visitNonOptionalParenthesizedExpression(node: ParenthesizedExpression, captureThisArg: boolean): Expression { + const expression = visitNonOptionalExpression(node.expression, captureThisArg); + if (isSyntheticReference(expression)) { + // `(a.b)` -> { expression `((_a = a).b)`, thisArg: `_a` } + // `(a[b])` -> { expression `((_a = a)[b])`, thisArg: `_a` } + return createSyntheticReferenceExpression(updateParen(node, expression.expression), expression.thisArg); + } + return updateParen(node, expression); + } + + function visitNonOptionalPropertyAccessExpression(node: PropertyAccessExpression, captureThisArg: boolean): Expression { + if (isOptionalChain(node)) { + // If `node` is an optional chain, then it is the outermost chain of an optional expression. + return visitOptionalExpression(node, captureThisArg); + } + + let expression = visitNode(node.expression, visitor, isExpression); + Debug.assertNotNode(expression, isSyntheticReference); + + let thisArg: Expression | undefined; + if (captureThisArg) { + // `a.b` -> { expression: `(_a = a).b`, thisArg: `_a` } + thisArg = createTempVariable(hoistVariableDeclaration); + expression = createParen(createAssignment(thisArg, expression)); + } + + expression = updatePropertyAccess(node, expression, visitNode(node.name, visitor, isIdentifier)); + return thisArg ? createSyntheticReferenceExpression(expression, thisArg) : expression; + } + + function visitNonOptionalElementAccessExpression(node: ElementAccessExpression, captureThisArg: boolean): Expression { + if (isOptionalChain(node)) { + // If `node` is an optional chain, then it is the outermost chain of an optional expression. + return visitOptionalExpression(node, captureThisArg); + } + + let expression = visitNode(node.expression, visitor, isExpression); + Debug.assertNotNode(expression, isSyntheticReference); + + let thisArg: Expression | undefined; + if (captureThisArg) { + // `a[b]` -> { expression: `(_a = a)[b]`, thisArg: `_a` } + thisArg = createTempVariable(hoistVariableDeclaration); + expression = createParen(createAssignment(thisArg, expression)); + } + + expression = updateElementAccess(node, expression, visitNode(node.argumentExpression, visitor, isExpression)); + return thisArg ? createSyntheticReferenceExpression(expression, thisArg) : expression; + } + + function visitNonOptionalCallExpression(node: CallExpression, captureThisArg: boolean): Expression { + if (isOptionalChain(node)) { + // If `node` is an optional chain, then it is the outermost chain of an optional expression. + return visitOptionalExpression(node, captureThisArg); + } + return visitEachChild(node, visitor, context); + } + + function visitNonOptionalExpression(node: Expression, captureThisArg: boolean): Expression { + switch (node.kind) { + case SyntaxKind.ParenthesizedExpression: return visitNonOptionalParenthesizedExpression(node as ParenthesizedExpression, captureThisArg); + case SyntaxKind.PropertyAccessExpression: return visitNonOptionalPropertyAccessExpression(node as PropertyAccessExpression, captureThisArg); + case SyntaxKind.ElementAccessExpression: return visitNonOptionalElementAccessExpression(node as ElementAccessExpression, captureThisArg); + case SyntaxKind.CallExpression: return visitNonOptionalCallExpression(node as CallExpression, captureThisArg); + default: return visitNode(node, visitor, isExpression); + } + } + + function visitOptionalExpression(node: OptionalChain, captureThisArg: boolean): Expression { + const { expression, chain } = flattenChain(node); + const left = visitNonOptionalExpression(expression, isCallChain(chain[0])); + const temp = createTempVariable(hoistVariableDeclaration); + const leftThisArg = isSyntheticReference(left) ? left.thisArg : undefined; + const leftExpression = isSyntheticReference(left) ? left.expression : left; + let rightExpression: Expression = temp; + let thisArg: Expression | undefined; + for (let i = 0; i < chain.length; i++) { + const segment = chain[i]; + switch (segment.kind) { + case SyntaxKind.PropertyAccessExpression: + if (i === chain.length - 1 && captureThisArg) { + thisArg = createTempVariable(hoistVariableDeclaration); + rightExpression = createParen(createAssignment(thisArg, rightExpression)); + } + rightExpression = createPropertyAccess( + rightExpression, + visitNode(segment.name, visitor, isIdentifier) + ); + break; + case SyntaxKind.ElementAccessExpression: + if (i === chain.length - 1 && captureThisArg) { + thisArg = createTempVariable(hoistVariableDeclaration); + rightExpression = createParen(createAssignment(thisArg, rightExpression)); + } + rightExpression = createElementAccess( + rightExpression, + visitNode(segment.argumentExpression, visitor, isExpression) + ); + break; + case SyntaxKind.CallExpression: + if (i === 0 && leftThisArg) { + rightExpression = createFunctionCall( + rightExpression, + leftThisArg, + visitNodes(segment.arguments, visitor, isExpression) + ); + } + else { + rightExpression = createCall( + rightExpression, + /*typeArguments*/ undefined, + visitNodes(segment.arguments, visitor, isExpression) + ); + } + break; + } + setOriginalNode(rightExpression, segment); + } + + const target = createConditional( + createLogicalOr( + createStrictEquality(createAssignment(temp, leftExpression), createNull()), + createStrictEquality(temp, createVoidZero()) + ), + createVoidZero(), + rightExpression + ); + return thisArg ? createSyntheticReferenceExpression(target, thisArg) : target; + } } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 999a922041201..3408ca33bf4b3 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -151,6 +151,7 @@ namespace ts { DotDotDotToken, SemicolonToken, CommaToken, + QuestionDotToken, LessThanToken, LessThanSlashToken, GreaterThanToken, @@ -483,6 +484,7 @@ namespace ts { CommaListExpression, MergeDeclarationMarker, EndOfDeclarationMarker, + SyntheticReferenceExpression, // Enum value count Count, @@ -528,20 +530,21 @@ namespace ts { NestedNamespace = 1 << 2, // Namespace declaration Synthesized = 1 << 3, // Node was synthesized during transformation Namespace = 1 << 4, // Namespace declaration - 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 + OptionalChain = 1 << 5, // Chained MemberExpression rooted to a pseudo-OptionalExpression + ExportContext = 1 << 6, // Export context (initialized by binding) + ContainsThis = 1 << 7, // Interface contains references to "this" + HasImplicitReturn = 1 << 8, // If function implicitly returns on one of codepaths (initialized by binding) + HasExplicitReturn = 1 << 9, // If function has explicit reachable return on one of codepaths (initialized by binding) + GlobalAugmentation = 1 << 10, // Set if module declaration is an augmentation for the global scope + HasAsyncFunctions = 1 << 11, // If the file has async functions (initialized by binding) + DisallowInContext = 1 << 12, // If node was parsed in a context where 'in-expressions' are not allowed + YieldContext = 1 << 13, // If node was parsed in the 'yield' context created when parsing a generator + DecoratorContext = 1 << 14, // If node was parsed as part of a decorator + AwaitContext = 1 << 15, // If node was parsed in the 'await' context created when parsing an async function + ThisNodeHasError = 1 << 16, // If the parser encountered an error when parsing the code that created this node + JavaScriptFile = 1 << 17, // If node was parsed in a JavaScript + ThisNodeOrAnySubNodesHasError = 1 << 18, // If this node or any of its children had an error + HasAggregatedChildData = 1 << 19, // If we've computed data from children and cached it in this node // These flags will be set when the parser encounters a dynamic import expression or 'import.meta' to avoid // walking the tree if the flags are not set. However, these flags are just a approximation @@ -552,13 +555,13 @@ namespace ts { // removal, it is likely that users will add the import anyway. // 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 << 19, - /* @internal */ PossiblyContainsImportMeta = 1 << 20, + /* @internal */ PossiblyContainsDynamicImport = 1 << 20, + /* @internal */ PossiblyContainsImportMeta = 1 << 21, - JSDoc = 1 << 21, // If node was parsed inside jsdoc - /* @internal */ Ambient = 1 << 22, // If node was inside an ambient context -- a declaration file, or inside something with the `declare` modifier. - /* @internal */ InWithStatement = 1 << 23, // If any ancestor of node was the `statement` of a WithStatement (not the `expression`) - JsonFile = 1 << 24, // If node was parsed in a Json + JSDoc = 1 << 22, // If node was parsed inside jsdoc + /* @internal */ Ambient = 1 << 23, // If node was inside an ambient context -- a declaration file, or inside something with the `declare` modifier. + /* @internal */ InWithStatement = 1 << 24, // If any ancestor of node was the `statement` of a WithStatement (not the `expression`) + JsonFile = 1 << 25, // If node was parsed in a Json BlockScoped = Let | Const, @@ -724,8 +727,10 @@ namespace ts { kind: TKind; } + export type DotToken = Token; export type DotDotDotToken = Token; export type QuestionToken = Token; + export type QuestionDotToken = Token; export type ExclamationToken = Token; export type ColonToken = Token; export type EqualsToken = Token; @@ -1776,9 +1781,15 @@ namespace ts { export interface PropertyAccessExpression extends MemberExpression, NamedDeclaration { kind: SyntaxKind.PropertyAccessExpression; expression: LeftHandSideExpression; + questionDotToken?: QuestionDotToken; name: Identifier; } + export interface PropertyAccessChain extends PropertyAccessExpression { + _optionalChainBrand: any; + kind: SyntaxKind.PropertyAccessExpression; + } + export interface SuperPropertyAccessExpression extends PropertyAccessExpression { expression: SuperExpression; } @@ -1792,9 +1803,15 @@ namespace ts { export interface ElementAccessExpression extends MemberExpression { kind: SyntaxKind.ElementAccessExpression; expression: LeftHandSideExpression; + questionDotToken?: QuestionDotToken; argumentExpression: Expression; } + export interface ElementAccessChain extends ElementAccessExpression { + _optionalChainBrand: any; + kind: SyntaxKind.ElementAccessExpression; + } + export interface SuperElementAccessExpression extends ElementAccessExpression { expression: SuperExpression; } @@ -1805,10 +1822,22 @@ namespace ts { export interface CallExpression extends LeftHandSideExpression, Declaration { kind: SyntaxKind.CallExpression; expression: LeftHandSideExpression; + questionDotToken?: QuestionDotToken; typeArguments?: NodeArray; arguments: NodeArray; } + export interface CallChain extends CallExpression { + _optionalChainBrand: any; + kind: SyntaxKind.CallExpression; + } + + export type OptionalChain = + | PropertyAccessChain + | ElementAccessChain + | CallChain + ; + /** @internal */ export type BindableObjectDefinePropertyCall = CallExpression & { arguments: { 0: EntityNameExpression, 1: StringLiteralLike | NumericLiteral, 2: ObjectLiteralExpression } }; @@ -1839,6 +1868,7 @@ namespace ts { tag: LeftHandSideExpression; typeArguments?: NodeArray; template: TemplateLiteral; + /*@internal*/ questionDotToken?: QuestionDotToken; // NOTE: Invalid syntax, only used to report a grammar error. } export type CallLikeExpression = CallExpression | NewExpression | TaggedTemplateExpression | Decorator | JsxOpeningLikeElement; @@ -2006,6 +2036,13 @@ namespace ts { kind: SyntaxKind.MergeDeclarationMarker; } + /* @internal */ + export interface SyntheticReferenceExpression extends LeftHandSideExpression { + kind: SyntaxKind.SyntheticReferenceExpression; + expression: Expression; + thisArg: Expression; + } + export interface EmptyStatement extends Statement { kind: SyntaxKind.EmptyStatement; } @@ -3162,6 +3199,7 @@ namespace ts { /* @internal */ getParameterType(signature: Signature, parameterIndex: number): Type; getNullableType(type: Type, flags: TypeFlags): Type; getNonNullableType(type: Type): Type; + /* @internal */ getNonOptionalType(type: Type): Type; // TODO: GH#18217 `xToDeclaration` calls are frequently asserted as defined. /** Note that the resulting nodes cannot be checked. */ @@ -3281,6 +3319,7 @@ namespace ts { /* @internal */ getNullType(): Type; /* @internal */ getESSymbolType(): Type; /* @internal */ getNeverType(): Type; + /* @internal */ getOptionalType(): Type; /* @internal */ getUnionType(types: Type[], subtypeReduction?: UnionReduction): Type; /* @internal */ createArrayType(elementType: Type): Type; /* @internal */ getElementTypeOfArrayType(arrayType: Type): Type | undefined; @@ -4477,6 +4516,8 @@ namespace ts { 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 { @@ -5886,6 +5927,8 @@ namespace ts { getColumn(): number; getIndent(): number; isAtStartOfLine(): boolean; + hasPrecedingComment(): boolean; + hasPrecedingWhitespace(): boolean; getTextPosWithWriteLine?(): number; } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index facc13ada282f..95daabfabab2c 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -57,7 +57,6 @@ namespace ts { function createSingleLineStringWriter(): EmitTextWriter { let str = ""; - const writeText: (text: string) => void = text => str += text; return { getText: () => str, @@ -79,6 +78,8 @@ namespace ts { getColumn: () => 0, getIndent: () => 0, isAtStartOfLine: () => false, + hasPrecedingComment: () => false, + hasPrecedingWhitespace: () => !!str.length && isWhiteSpaceLike(str.charCodeAt(str.length - 1)), // Completely ignore indentation for string writers. And map newlines to // a single space. @@ -3282,6 +3283,7 @@ namespace ts { let lineStart: boolean; let lineCount: number; let linePos: number; + let hasPrecedingComment = false; function updateLineCountAndPosFor(s: string) { const lineStartsOfS = computeLineStarts(s); @@ -3295,7 +3297,7 @@ namespace ts { } } - function write(s: string) { + function writeText(s: string) { if (s && s.length) { if (lineStart) { s = getIndentString(indent) + s; @@ -3306,18 +3308,30 @@ namespace ts { } } + function write(s: string) { + if (s) hasPrecedingComment = false; + writeText(s); + } + + function writeComment(s: string) { + if (s) hasPrecedingComment = true; + writeText(s); + } + function reset(): void { output = ""; indent = 0; lineStart = true; lineCount = 0; linePos = 0; + hasPrecedingComment = false; } function rawWrite(s: string) { if (s !== undefined) { output += s; updateLineCountAndPosFor(s); + hasPrecedingComment = false; } } @@ -3333,6 +3347,7 @@ namespace ts { lineCount++; linePos = output.length; lineStart = true; + hasPrecedingComment = false; } } @@ -3355,6 +3370,8 @@ namespace ts { getColumn: () => lineStart ? indent * getIndentSize() : output.length - linePos, getText: () => output, isAtStartOfLine: () => lineStart, + hasPrecedingComment: () => hasPrecedingComment, + hasPrecedingWhitespace: () => !!output.length && isWhiteSpaceLike(output.charCodeAt(output.length - 1)), clear: reset, reportInaccessibleThisError: noop, reportPrivateInBaseOfClassExpression: noop, @@ -3369,7 +3386,7 @@ namespace ts { writeStringLiteral: write, writeSymbol: (s, _) => write(s), writeTrailingSemicolon: write, - writeComment: write, + writeComment, getTextPosWithWriteLine }; } @@ -4730,6 +4747,14 @@ namespace ts { return false; } } + + export function getDotOrQuestionDotToken(node: PropertyAccessExpression) { + if (node.questionDotToken) { + return node.questionDotToken; + } + + return createNode(SyntaxKind.DotToken, node.expression.end, node.name.pos) as DotToken; + } } namespace ts { @@ -5734,14 +5759,32 @@ namespace ts { return node.kind === SyntaxKind.PropertyAccessExpression; } + export function isPropertyAccessChain(node: Node): node is PropertyAccessChain { + return isPropertyAccessExpression(node) && !!(node.flags & NodeFlags.OptionalChain); + } + export function isElementAccessExpression(node: Node): node is ElementAccessExpression { return node.kind === SyntaxKind.ElementAccessExpression; } + export function isElementAccessChain(node: Node): node is ElementAccessChain { + return isElementAccessExpression(node) && !!(node.flags & NodeFlags.OptionalChain); + } + export function isCallExpression(node: Node): node is CallExpression { return node.kind === SyntaxKind.CallExpression; } + export function isCallChain(node: Node): node is CallChain { + return isCallExpression(node) && !!(node.flags & NodeFlags.OptionalChain); + } + + export function isOptionalChain(node: Node): node is PropertyAccessChain | ElementAccessChain | CallChain { + return isPropertyAccessChain(node) + || isElementAccessChain(node) + || isCallChain(node); + } + export function isNewExpression(node: Node): node is NewExpression { return node.kind === SyntaxKind.NewExpression; } @@ -6750,6 +6793,11 @@ namespace ts { return node.kind === SyntaxKind.NotEmittedStatement; } + /* @internal */ + export function isSyntheticReference(node: Node): node is SyntheticReferenceExpression { + return node.kind === SyntaxKind.SyntheticReferenceExpression; + } + /* @internal */ export function isNotEmittedOrPartiallyEmittedNode(node: Node): node is NotEmittedStatement | PartiallyEmittedExpression { return isNotEmittedStatement(node) diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index 45b43c1d761ff..f6d766679ee4e 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -460,16 +460,35 @@ namespace ts { nodesVisitor((node).properties, visitor, isObjectLiteralElementLike)); case SyntaxKind.PropertyAccessExpression: + if (node.flags & NodeFlags.OptionalChain) { + return updatePropertyAccessChain(node, + visitNode((node).expression, visitor, isExpression), + visitNode((node).questionDotToken, visitor, isToken), + visitNode((node).name, visitor, isIdentifier)); + } return updatePropertyAccess(node, visitNode((node).expression, visitor, isExpression), visitNode((node).name, visitor, isIdentifier)); case SyntaxKind.ElementAccessExpression: + if (node.flags & NodeFlags.OptionalChain) { + return updateElementAccessChain(node, + visitNode((node).expression, visitor, isExpression), + visitNode((node).questionDotToken, visitor, isToken), + visitNode((node).argumentExpression, visitor, isExpression)); + } return updateElementAccess(node, visitNode((node).expression, visitor, isExpression), visitNode((node).argumentExpression, visitor, isExpression)); case SyntaxKind.CallExpression: + if (node.flags & NodeFlags.OptionalChain) { + return updateCallChain(node, + visitNode((node).expression, visitor, isExpression), + visitNode((node).questionDotToken, visitor, isToken), + nodesVisitor((node).typeArguments, visitor, isTypeNode), + nodesVisitor((node).arguments, visitor, isExpression)); + } return updateCall(node, visitNode((node).expression, visitor, isExpression), nodesVisitor((node).typeArguments, visitor, isTypeNode), diff --git a/src/services/completions.ts b/src/services/completions.ts index 3ee843c38da4c..9d5fef67f2900 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -767,6 +767,8 @@ namespace ts.Completions { let node = currentToken; let propertyAccessToConvert: PropertyAccessExpression | undefined; let isRightOfDot = false; + let isRightOfQuestionDot = false; + let isOptional = false; let isRightOfOpenTag = false; let isStartingCloseTag = false; let isJsxInitializer: IsJsxInitializer = false; @@ -780,10 +782,12 @@ namespace ts.Completions { } let parent = contextToken.parent; - if (contextToken.kind === SyntaxKind.DotToken) { - isRightOfDot = true; + if (contextToken.kind === SyntaxKind.DotToken || contextToken.kind === SyntaxKind.QuestionDotToken) { + isRightOfDot = contextToken.kind === SyntaxKind.DotToken; + isRightOfQuestionDot = contextToken.kind === SyntaxKind.QuestionDotToken; switch (parent.kind) { case SyntaxKind.PropertyAccessExpression: + isOptional = isOptionalChain(parent); propertyAccessToConvert = parent as PropertyAccessExpression; node = propertyAccessToConvert.expression; if (node.end === contextToken.pos && @@ -890,6 +894,10 @@ namespace ts.Completions { if (isRightOfDot) { getTypeScriptMemberSymbols(); } + else if (isRightOfQuestionDot) { + getTypeScriptMemberSymbols(); + isNewIdentifierLocation = true; + } else if (isRightOfOpenTag) { const tagSymbols = Debug.assertEachDefined(typeChecker.getJsxIntrinsicTagNamesAt(location), "getJsxIntrinsicTagNames() should all be defined"); tryGetGlobalSymbols(); @@ -995,7 +1003,7 @@ namespace ts.Completions { if (!isTypeLocation && symbol.declarations && symbol.declarations.some(d => d.kind !== SyntaxKind.SourceFile && d.kind !== SyntaxKind.ModuleDeclaration && d.kind !== SyntaxKind.EnumDeclaration)) { - addTypeProperties(typeChecker.getTypeOfSymbolAtLocation(symbol, node), !!(node.flags & NodeFlags.AwaitContext)); + addTypeProperties(removeOptionality(typeChecker.getTypeOfSymbolAtLocation(symbol, node), isRightOfQuestionDot, isOptional), !!(node.flags & NodeFlags.AwaitContext)); } return; @@ -1010,7 +1018,7 @@ namespace ts.Completions { } if (!isTypeLocation) { - addTypeProperties(typeChecker.getTypeAtLocation(node), !!(node.flags & NodeFlags.AwaitContext)); + addTypeProperties(removeOptionality(typeChecker.getTypeAtLocation(node), isRightOfQuestionDot, isOptional), !!(node.flags & NodeFlags.AwaitContext)); } } diff --git a/src/services/services.ts b/src/services/services.ts index ad8528b65af65..d6a26610d6925 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -414,6 +414,9 @@ namespace ts { getNonNullableType(): Type { return this.checker.getNonNullableType(this); } + getNonOptionalType(): Type { + return this.checker.getNonOptionalType(this); + } getConstraint(): Type | undefined { return this.checker.getBaseConstraintOfType(this); } diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index 31c9f9f26717b..cd073447a7d33 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -897,7 +897,6 @@ namespace ts.textChanges { function createWriter(newLine: string, omitTrailingSemicolon?: boolean): TextChangesWriter { let lastNonTriviaPosition = 0; - const writer = omitTrailingSemicolon ? getTrailingSemicolonOmittingWriter(createTextWriter(newLine)) : createTextWriter(newLine); const onEmitNode: PrintHandlers["onEmitNode"] = (hint, node, printCallback) => { if (node) { @@ -1052,6 +1051,8 @@ namespace ts.textChanges { getColumn, getIndent, isAtStartOfLine, + hasPrecedingComment: () => writer.hasPrecedingComment(), + hasPrecedingWhitespace: () => writer.hasPrecedingWhitespace(), clear }; } diff --git a/src/services/types.ts b/src/services/types.ts index 53d552394e0df..91e47a5f4ed27 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -50,6 +50,7 @@ namespace ts { getNumberIndexType(): Type | undefined; getBaseTypes(): BaseType[] | undefined; getNonNullableType(): Type; + /*@internal*/ getNonOptionalType(): Type; getConstraint(): Type | undefined; getDefault(): Type | undefined; diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 2e6e6875a8159..540f4f87c08ba 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -950,6 +950,12 @@ namespace ts { } } + export function removeOptionality(type: Type, isOptionalExpression: boolean, isOptionalChain: boolean) { + return isOptionalExpression ? type.getNonNullableType() : + isOptionalChain ? type.getNonOptionalType() : + type; + } + export function isPossiblyTypeArgumentPosition(token: Node, sourceFile: SourceFile, checker: TypeChecker): boolean { const info = getPossibleTypeArgumentsInfo(token, sourceFile); return info !== undefined && (isPartOfTypeNode(info.called) || @@ -958,7 +964,11 @@ namespace ts { } export function getPossibleGenericSignatures(called: Expression, typeArgumentCount: number, checker: TypeChecker): ReadonlyArray { - const type = checker.getTypeAtLocation(called); + let type = checker.getTypeAtLocation(called); + if (isOptionalChain(called.parent)) { + type = removeOptionality(type, !!called.parent.questionDotToken, /*isOptionalChain*/ true); + } + const signatures = isNewExpression(called.parent) ? type.getConstructSignatures() : type.getCallSignatures(); return signatures.filter(candidate => !!candidate.typeParameters && candidate.typeParameters.length >= typeArgumentCount); } @@ -987,6 +997,9 @@ namespace ts { case SyntaxKind.LessThanToken: // Found the beginning of the generic argument expression token = findPrecedingToken(token.getFullStart(), sourceFile); + if (token && token.kind === SyntaxKind.QuestionDotToken) { + token = findPrecedingToken(token.getFullStart(), sourceFile); + } if (!token || !isIdentifier(token)) return undefined; if (!remainingLessThanTokens) { return isDeclarationName(token) ? undefined : { called: token, nTypeArguments }; @@ -1485,6 +1498,8 @@ namespace ts { getColumn: () => 0, getLine: () => 0, isAtStartOfLine: () => false, + hasPrecedingWhitespace: () => false, + hasPrecedingComment: () => false, rawWrite: notImplemented, getIndent: () => indent, increaseIndent: () => { indent++; }, diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 67d6a98e11ec5..4bfc0c5e1bb7b 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -104,329 +104,331 @@ declare namespace ts { DotDotDotToken = 25, SemicolonToken = 26, CommaToken = 27, - LessThanToken = 28, - LessThanSlashToken = 29, - GreaterThanToken = 30, - LessThanEqualsToken = 31, - GreaterThanEqualsToken = 32, - EqualsEqualsToken = 33, - ExclamationEqualsToken = 34, - EqualsEqualsEqualsToken = 35, - ExclamationEqualsEqualsToken = 36, - EqualsGreaterThanToken = 37, - PlusToken = 38, - MinusToken = 39, - AsteriskToken = 40, - AsteriskAsteriskToken = 41, - SlashToken = 42, - PercentToken = 43, - PlusPlusToken = 44, - MinusMinusToken = 45, - LessThanLessThanToken = 46, - GreaterThanGreaterThanToken = 47, - GreaterThanGreaterThanGreaterThanToken = 48, - AmpersandToken = 49, - BarToken = 50, - CaretToken = 51, - ExclamationToken = 52, - TildeToken = 53, - AmpersandAmpersandToken = 54, - BarBarToken = 55, - QuestionToken = 56, - ColonToken = 57, - AtToken = 58, + QuestionDotToken = 28, + LessThanToken = 29, + LessThanSlashToken = 30, + GreaterThanToken = 31, + LessThanEqualsToken = 32, + GreaterThanEqualsToken = 33, + EqualsEqualsToken = 34, + ExclamationEqualsToken = 35, + EqualsEqualsEqualsToken = 36, + ExclamationEqualsEqualsToken = 37, + EqualsGreaterThanToken = 38, + PlusToken = 39, + MinusToken = 40, + AsteriskToken = 41, + AsteriskAsteriskToken = 42, + SlashToken = 43, + PercentToken = 44, + PlusPlusToken = 45, + MinusMinusToken = 46, + LessThanLessThanToken = 47, + GreaterThanGreaterThanToken = 48, + GreaterThanGreaterThanGreaterThanToken = 49, + AmpersandToken = 50, + BarToken = 51, + CaretToken = 52, + ExclamationToken = 53, + TildeToken = 54, + AmpersandAmpersandToken = 55, + BarBarToken = 56, + QuestionToken = 57, + ColonToken = 58, + AtToken = 59, /** Only the JSDoc scanner produces BacktickToken. The normal scanner produces NoSubstitutionTemplateLiteral and related kinds. */ - BacktickToken = 59, - EqualsToken = 60, - PlusEqualsToken = 61, - MinusEqualsToken = 62, - AsteriskEqualsToken = 63, - AsteriskAsteriskEqualsToken = 64, - SlashEqualsToken = 65, - PercentEqualsToken = 66, - LessThanLessThanEqualsToken = 67, - GreaterThanGreaterThanEqualsToken = 68, - GreaterThanGreaterThanGreaterThanEqualsToken = 69, - AmpersandEqualsToken = 70, - BarEqualsToken = 71, - CaretEqualsToken = 72, - Identifier = 73, - BreakKeyword = 74, - CaseKeyword = 75, - CatchKeyword = 76, - ClassKeyword = 77, - ConstKeyword = 78, - ContinueKeyword = 79, - DebuggerKeyword = 80, - DefaultKeyword = 81, - DeleteKeyword = 82, - DoKeyword = 83, - ElseKeyword = 84, - EnumKeyword = 85, - ExportKeyword = 86, - ExtendsKeyword = 87, - FalseKeyword = 88, - FinallyKeyword = 89, - ForKeyword = 90, - FunctionKeyword = 91, - IfKeyword = 92, - ImportKeyword = 93, - InKeyword = 94, - InstanceOfKeyword = 95, - NewKeyword = 96, - NullKeyword = 97, - ReturnKeyword = 98, - SuperKeyword = 99, - SwitchKeyword = 100, - ThisKeyword = 101, - ThrowKeyword = 102, - TrueKeyword = 103, - TryKeyword = 104, - TypeOfKeyword = 105, - VarKeyword = 106, - VoidKeyword = 107, - WhileKeyword = 108, - WithKeyword = 109, - ImplementsKeyword = 110, - InterfaceKeyword = 111, - LetKeyword = 112, - PackageKeyword = 113, - PrivateKeyword = 114, - ProtectedKeyword = 115, - PublicKeyword = 116, - StaticKeyword = 117, - YieldKeyword = 118, - AbstractKeyword = 119, - AsKeyword = 120, - AnyKeyword = 121, - AsyncKeyword = 122, - AwaitKeyword = 123, - BooleanKeyword = 124, - ConstructorKeyword = 125, - DeclareKeyword = 126, - GetKeyword = 127, - InferKeyword = 128, - IsKeyword = 129, - KeyOfKeyword = 130, - ModuleKeyword = 131, - NamespaceKeyword = 132, - NeverKeyword = 133, - ReadonlyKeyword = 134, - RequireKeyword = 135, - NumberKeyword = 136, - ObjectKeyword = 137, - SetKeyword = 138, - StringKeyword = 139, - SymbolKeyword = 140, - TypeKeyword = 141, - UndefinedKeyword = 142, - UniqueKeyword = 143, - UnknownKeyword = 144, - FromKeyword = 145, - GlobalKeyword = 146, - BigIntKeyword = 147, - OfKeyword = 148, - QualifiedName = 149, - ComputedPropertyName = 150, - TypeParameter = 151, - Parameter = 152, - Decorator = 153, - PropertySignature = 154, - PropertyDeclaration = 155, - MethodSignature = 156, - MethodDeclaration = 157, - Constructor = 158, - GetAccessor = 159, - SetAccessor = 160, - CallSignature = 161, - ConstructSignature = 162, - IndexSignature = 163, - TypePredicate = 164, - TypeReference = 165, - FunctionType = 166, - ConstructorType = 167, - TypeQuery = 168, - TypeLiteral = 169, - ArrayType = 170, - TupleType = 171, - OptionalType = 172, - RestType = 173, - UnionType = 174, - IntersectionType = 175, - ConditionalType = 176, - InferType = 177, - ParenthesizedType = 178, - ThisType = 179, - TypeOperator = 180, - IndexedAccessType = 181, - MappedType = 182, - LiteralType = 183, - ImportType = 184, - ObjectBindingPattern = 185, - ArrayBindingPattern = 186, - BindingElement = 187, - ArrayLiteralExpression = 188, - ObjectLiteralExpression = 189, - PropertyAccessExpression = 190, - ElementAccessExpression = 191, - CallExpression = 192, - NewExpression = 193, - TaggedTemplateExpression = 194, - TypeAssertionExpression = 195, - ParenthesizedExpression = 196, - FunctionExpression = 197, - ArrowFunction = 198, - DeleteExpression = 199, - TypeOfExpression = 200, - VoidExpression = 201, - AwaitExpression = 202, - PrefixUnaryExpression = 203, - PostfixUnaryExpression = 204, - BinaryExpression = 205, - ConditionalExpression = 206, - TemplateExpression = 207, - YieldExpression = 208, - SpreadElement = 209, - ClassExpression = 210, - OmittedExpression = 211, - ExpressionWithTypeArguments = 212, - AsExpression = 213, - NonNullExpression = 214, - MetaProperty = 215, - SyntheticExpression = 216, - TemplateSpan = 217, - SemicolonClassElement = 218, - Block = 219, - VariableStatement = 220, - EmptyStatement = 221, - ExpressionStatement = 222, - IfStatement = 223, - DoStatement = 224, - WhileStatement = 225, - ForStatement = 226, - ForInStatement = 227, - ForOfStatement = 228, - ContinueStatement = 229, - BreakStatement = 230, - ReturnStatement = 231, - WithStatement = 232, - SwitchStatement = 233, - LabeledStatement = 234, - ThrowStatement = 235, - TryStatement = 236, - DebuggerStatement = 237, - VariableDeclaration = 238, - VariableDeclarationList = 239, - FunctionDeclaration = 240, - ClassDeclaration = 241, - InterfaceDeclaration = 242, - TypeAliasDeclaration = 243, - EnumDeclaration = 244, - ModuleDeclaration = 245, - ModuleBlock = 246, - CaseBlock = 247, - NamespaceExportDeclaration = 248, - ImportEqualsDeclaration = 249, - ImportDeclaration = 250, - ImportClause = 251, - NamespaceImport = 252, - NamedImports = 253, - ImportSpecifier = 254, - ExportAssignment = 255, - ExportDeclaration = 256, - NamedExports = 257, - ExportSpecifier = 258, - MissingDeclaration = 259, - ExternalModuleReference = 260, - JsxElement = 261, - JsxSelfClosingElement = 262, - JsxOpeningElement = 263, - JsxClosingElement = 264, - JsxFragment = 265, - JsxOpeningFragment = 266, - JsxClosingFragment = 267, - JsxAttribute = 268, - JsxAttributes = 269, - JsxSpreadAttribute = 270, - JsxExpression = 271, - CaseClause = 272, - DefaultClause = 273, - HeritageClause = 274, - CatchClause = 275, - PropertyAssignment = 276, - ShorthandPropertyAssignment = 277, - SpreadAssignment = 278, - EnumMember = 279, - UnparsedPrologue = 280, - UnparsedPrepend = 281, - UnparsedText = 282, - UnparsedInternalText = 283, - UnparsedSyntheticReference = 284, - SourceFile = 285, - Bundle = 286, - UnparsedSource = 287, - InputFiles = 288, - JSDocTypeExpression = 289, - JSDocAllType = 290, - JSDocUnknownType = 291, - JSDocNullableType = 292, - JSDocNonNullableType = 293, - JSDocOptionalType = 294, - JSDocFunctionType = 295, - JSDocVariadicType = 296, - JSDocNamepathType = 297, - JSDocComment = 298, - JSDocTypeLiteral = 299, - JSDocSignature = 300, - JSDocTag = 301, - JSDocAugmentsTag = 302, - JSDocAuthorTag = 303, - JSDocClassTag = 304, - JSDocCallbackTag = 305, - JSDocEnumTag = 306, - JSDocParameterTag = 307, - JSDocReturnTag = 308, - JSDocThisTag = 309, - JSDocTypeTag = 310, - JSDocTemplateTag = 311, - JSDocTypedefTag = 312, - JSDocPropertyTag = 313, - SyntaxList = 314, - NotEmittedStatement = 315, - PartiallyEmittedExpression = 316, - CommaListExpression = 317, - MergeDeclarationMarker = 318, - EndOfDeclarationMarker = 319, - Count = 320, - FirstAssignment = 60, - LastAssignment = 72, - FirstCompoundAssignment = 61, - LastCompoundAssignment = 72, - FirstReservedWord = 74, - LastReservedWord = 109, - FirstKeyword = 74, - LastKeyword = 148, - FirstFutureReservedWord = 110, - LastFutureReservedWord = 118, - FirstTypeNode = 164, - LastTypeNode = 184, + BacktickToken = 60, + EqualsToken = 61, + PlusEqualsToken = 62, + MinusEqualsToken = 63, + AsteriskEqualsToken = 64, + AsteriskAsteriskEqualsToken = 65, + SlashEqualsToken = 66, + PercentEqualsToken = 67, + LessThanLessThanEqualsToken = 68, + GreaterThanGreaterThanEqualsToken = 69, + GreaterThanGreaterThanGreaterThanEqualsToken = 70, + AmpersandEqualsToken = 71, + BarEqualsToken = 72, + CaretEqualsToken = 73, + Identifier = 74, + BreakKeyword = 75, + CaseKeyword = 76, + CatchKeyword = 77, + ClassKeyword = 78, + ConstKeyword = 79, + ContinueKeyword = 80, + DebuggerKeyword = 81, + DefaultKeyword = 82, + DeleteKeyword = 83, + DoKeyword = 84, + ElseKeyword = 85, + EnumKeyword = 86, + ExportKeyword = 87, + ExtendsKeyword = 88, + FalseKeyword = 89, + FinallyKeyword = 90, + ForKeyword = 91, + FunctionKeyword = 92, + IfKeyword = 93, + ImportKeyword = 94, + InKeyword = 95, + InstanceOfKeyword = 96, + NewKeyword = 97, + NullKeyword = 98, + ReturnKeyword = 99, + SuperKeyword = 100, + SwitchKeyword = 101, + ThisKeyword = 102, + ThrowKeyword = 103, + TrueKeyword = 104, + TryKeyword = 105, + TypeOfKeyword = 106, + VarKeyword = 107, + VoidKeyword = 108, + WhileKeyword = 109, + WithKeyword = 110, + ImplementsKeyword = 111, + InterfaceKeyword = 112, + LetKeyword = 113, + PackageKeyword = 114, + PrivateKeyword = 115, + ProtectedKeyword = 116, + PublicKeyword = 117, + StaticKeyword = 118, + YieldKeyword = 119, + AbstractKeyword = 120, + AsKeyword = 121, + AnyKeyword = 122, + AsyncKeyword = 123, + AwaitKeyword = 124, + BooleanKeyword = 125, + ConstructorKeyword = 126, + DeclareKeyword = 127, + GetKeyword = 128, + InferKeyword = 129, + IsKeyword = 130, + KeyOfKeyword = 131, + ModuleKeyword = 132, + NamespaceKeyword = 133, + NeverKeyword = 134, + ReadonlyKeyword = 135, + RequireKeyword = 136, + NumberKeyword = 137, + ObjectKeyword = 138, + SetKeyword = 139, + StringKeyword = 140, + SymbolKeyword = 141, + TypeKeyword = 142, + UndefinedKeyword = 143, + UniqueKeyword = 144, + UnknownKeyword = 145, + FromKeyword = 146, + GlobalKeyword = 147, + BigIntKeyword = 148, + OfKeyword = 149, + QualifiedName = 150, + ComputedPropertyName = 151, + TypeParameter = 152, + Parameter = 153, + Decorator = 154, + PropertySignature = 155, + PropertyDeclaration = 156, + MethodSignature = 157, + MethodDeclaration = 158, + Constructor = 159, + GetAccessor = 160, + SetAccessor = 161, + CallSignature = 162, + ConstructSignature = 163, + IndexSignature = 164, + TypePredicate = 165, + TypeReference = 166, + FunctionType = 167, + ConstructorType = 168, + TypeQuery = 169, + TypeLiteral = 170, + ArrayType = 171, + TupleType = 172, + OptionalType = 173, + RestType = 174, + UnionType = 175, + IntersectionType = 176, + ConditionalType = 177, + InferType = 178, + ParenthesizedType = 179, + ThisType = 180, + TypeOperator = 181, + IndexedAccessType = 182, + MappedType = 183, + LiteralType = 184, + ImportType = 185, + ObjectBindingPattern = 186, + ArrayBindingPattern = 187, + BindingElement = 188, + ArrayLiteralExpression = 189, + ObjectLiteralExpression = 190, + PropertyAccessExpression = 191, + ElementAccessExpression = 192, + CallExpression = 193, + NewExpression = 194, + TaggedTemplateExpression = 195, + TypeAssertionExpression = 196, + ParenthesizedExpression = 197, + FunctionExpression = 198, + ArrowFunction = 199, + DeleteExpression = 200, + TypeOfExpression = 201, + VoidExpression = 202, + AwaitExpression = 203, + PrefixUnaryExpression = 204, + PostfixUnaryExpression = 205, + BinaryExpression = 206, + ConditionalExpression = 207, + TemplateExpression = 208, + YieldExpression = 209, + SpreadElement = 210, + ClassExpression = 211, + OmittedExpression = 212, + ExpressionWithTypeArguments = 213, + AsExpression = 214, + NonNullExpression = 215, + MetaProperty = 216, + SyntheticExpression = 217, + TemplateSpan = 218, + SemicolonClassElement = 219, + Block = 220, + VariableStatement = 221, + EmptyStatement = 222, + ExpressionStatement = 223, + IfStatement = 224, + DoStatement = 225, + WhileStatement = 226, + ForStatement = 227, + ForInStatement = 228, + ForOfStatement = 229, + ContinueStatement = 230, + BreakStatement = 231, + ReturnStatement = 232, + WithStatement = 233, + SwitchStatement = 234, + LabeledStatement = 235, + ThrowStatement = 236, + TryStatement = 237, + DebuggerStatement = 238, + VariableDeclaration = 239, + VariableDeclarationList = 240, + FunctionDeclaration = 241, + ClassDeclaration = 242, + InterfaceDeclaration = 243, + TypeAliasDeclaration = 244, + EnumDeclaration = 245, + ModuleDeclaration = 246, + ModuleBlock = 247, + CaseBlock = 248, + NamespaceExportDeclaration = 249, + ImportEqualsDeclaration = 250, + ImportDeclaration = 251, + ImportClause = 252, + NamespaceImport = 253, + NamedImports = 254, + ImportSpecifier = 255, + ExportAssignment = 256, + ExportDeclaration = 257, + NamedExports = 258, + ExportSpecifier = 259, + MissingDeclaration = 260, + ExternalModuleReference = 261, + JsxElement = 262, + JsxSelfClosingElement = 263, + JsxOpeningElement = 264, + JsxClosingElement = 265, + JsxFragment = 266, + JsxOpeningFragment = 267, + JsxClosingFragment = 268, + JsxAttribute = 269, + JsxAttributes = 270, + JsxSpreadAttribute = 271, + JsxExpression = 272, + CaseClause = 273, + DefaultClause = 274, + HeritageClause = 275, + CatchClause = 276, + PropertyAssignment = 277, + ShorthandPropertyAssignment = 278, + SpreadAssignment = 279, + EnumMember = 280, + UnparsedPrologue = 281, + UnparsedPrepend = 282, + UnparsedText = 283, + UnparsedInternalText = 284, + UnparsedSyntheticReference = 285, + SourceFile = 286, + Bundle = 287, + UnparsedSource = 288, + InputFiles = 289, + JSDocTypeExpression = 290, + JSDocAllType = 291, + JSDocUnknownType = 292, + JSDocNullableType = 293, + JSDocNonNullableType = 294, + JSDocOptionalType = 295, + JSDocFunctionType = 296, + JSDocVariadicType = 297, + JSDocNamepathType = 298, + JSDocComment = 299, + JSDocTypeLiteral = 300, + JSDocSignature = 301, + JSDocTag = 302, + JSDocAugmentsTag = 303, + JSDocAuthorTag = 304, + JSDocClassTag = 305, + JSDocCallbackTag = 306, + JSDocEnumTag = 307, + JSDocParameterTag = 308, + JSDocReturnTag = 309, + JSDocThisTag = 310, + JSDocTypeTag = 311, + JSDocTemplateTag = 312, + JSDocTypedefTag = 313, + JSDocPropertyTag = 314, + SyntaxList = 315, + NotEmittedStatement = 316, + PartiallyEmittedExpression = 317, + CommaListExpression = 318, + MergeDeclarationMarker = 319, + EndOfDeclarationMarker = 320, + SyntheticReferenceExpression = 321, + Count = 322, + FirstAssignment = 61, + LastAssignment = 73, + FirstCompoundAssignment = 62, + LastCompoundAssignment = 73, + FirstReservedWord = 75, + LastReservedWord = 110, + FirstKeyword = 75, + LastKeyword = 149, + FirstFutureReservedWord = 111, + LastFutureReservedWord = 119, + FirstTypeNode = 165, + LastTypeNode = 185, FirstPunctuation = 18, - LastPunctuation = 72, + LastPunctuation = 73, FirstToken = 0, - LastToken = 148, + LastToken = 149, FirstTriviaToken = 2, LastTriviaToken = 7, FirstLiteralToken = 8, LastLiteralToken = 14, FirstTemplateToken = 14, LastTemplateToken = 17, - FirstBinaryOperator = 28, - LastBinaryOperator = 72, - FirstNode = 149, - FirstJSDocNode = 289, - LastJSDocNode = 313, - FirstJSDocTagNode = 301, - LastJSDocTagNode = 313, + FirstBinaryOperator = 29, + LastBinaryOperator = 73, + FirstNode = 150, + FirstJSDocNode = 290, + LastJSDocNode = 314, + FirstJSDocTagNode = 302, + LastJSDocTagNode = 314, } export enum NodeFlags { None = 0, @@ -435,27 +437,28 @@ declare namespace ts { NestedNamespace = 4, Synthesized = 8, Namespace = 16, - ExportContext = 32, - ContainsThis = 64, - HasImplicitReturn = 128, - HasExplicitReturn = 256, - GlobalAugmentation = 512, - HasAsyncFunctions = 1024, - DisallowInContext = 2048, - YieldContext = 4096, - DecoratorContext = 8192, - AwaitContext = 16384, - ThisNodeHasError = 32768, - JavaScriptFile = 65536, - ThisNodeOrAnySubNodesHasError = 131072, - HasAggregatedChildData = 262144, - JSDoc = 2097152, - JsonFile = 16777216, + OptionalChain = 32, + ExportContext = 64, + ContainsThis = 128, + HasImplicitReturn = 256, + HasExplicitReturn = 512, + GlobalAugmentation = 1024, + HasAsyncFunctions = 2048, + DisallowInContext = 4096, + YieldContext = 8192, + DecoratorContext = 16384, + AwaitContext = 32768, + ThisNodeHasError = 65536, + JavaScriptFile = 131072, + ThisNodeOrAnySubNodesHasError = 262144, + HasAggregatedChildData = 524288, + JSDoc = 4194304, + JsonFile = 33554432, BlockScoped = 3, - ReachabilityCheckFlags = 384, - ReachabilityAndEmitFlags = 1408, - ContextFlags = 12679168, - TypeExcludesFlags = 20480, + ReachabilityCheckFlags = 768, + ReachabilityAndEmitFlags = 2816, + ContextFlags = 25358336, + TypeExcludesFlags = 40960, } export enum ModifierFlags { None = 0, @@ -505,8 +508,10 @@ declare namespace ts { export interface Token extends Node { kind: TKind; } + export type DotToken = Token; export type DotDotDotToken = Token; export type QuestionToken = Token; + export type QuestionDotToken = Token; export type ExclamationToken = Token; export type ColonToken = Token; export type EqualsToken = Token; @@ -1077,8 +1082,13 @@ declare namespace ts { export interface PropertyAccessExpression extends MemberExpression, NamedDeclaration { kind: SyntaxKind.PropertyAccessExpression; expression: LeftHandSideExpression; + questionDotToken?: QuestionDotToken; name: Identifier; } + export interface PropertyAccessChain extends PropertyAccessExpression { + _optionalChainBrand: any; + kind: SyntaxKind.PropertyAccessExpression; + } export interface SuperPropertyAccessExpression extends PropertyAccessExpression { expression: SuperExpression; } @@ -1090,8 +1100,13 @@ declare namespace ts { export interface ElementAccessExpression extends MemberExpression { kind: SyntaxKind.ElementAccessExpression; expression: LeftHandSideExpression; + questionDotToken?: QuestionDotToken; argumentExpression: Expression; } + export interface ElementAccessChain extends ElementAccessExpression { + _optionalChainBrand: any; + kind: SyntaxKind.ElementAccessExpression; + } export interface SuperElementAccessExpression extends ElementAccessExpression { expression: SuperExpression; } @@ -1099,9 +1114,15 @@ declare namespace ts { export interface CallExpression extends LeftHandSideExpression, Declaration { kind: SyntaxKind.CallExpression; expression: LeftHandSideExpression; + questionDotToken?: QuestionDotToken; typeArguments?: NodeArray; arguments: NodeArray; } + export interface CallChain extends CallExpression { + _optionalChainBrand: any; + kind: SyntaxKind.CallExpression; + } + export type OptionalChain = PropertyAccessChain | ElementAccessChain | CallChain; export interface SuperCall extends CallExpression { expression: SuperExpression; } @@ -3445,8 +3466,12 @@ declare namespace ts { function isArrayLiteralExpression(node: Node): node is ArrayLiteralExpression; function isObjectLiteralExpression(node: Node): node is ObjectLiteralExpression; function isPropertyAccessExpression(node: Node): node is PropertyAccessExpression; + function isPropertyAccessChain(node: Node): node is PropertyAccessChain; function isElementAccessExpression(node: Node): node is ElementAccessExpression; + function isElementAccessChain(node: Node): node is ElementAccessChain; function isCallExpression(node: Node): node is CallExpression; + function isCallChain(node: Node): node is CallChain; + function isOptionalChain(node: Node): node is PropertyAccessChain | ElementAccessChain | CallChain; function isNewExpression(node: Node): node is NewExpression; function isTaggedTemplateExpression(node: Node): node is TaggedTemplateExpression; function isTypeAssertion(node: Node): node is TypeAssertion; @@ -3890,10 +3915,16 @@ declare namespace ts { function updateObjectLiteral(node: ObjectLiteralExpression, properties: ReadonlyArray): ObjectLiteralExpression; function createPropertyAccess(expression: Expression, name: string | Identifier): PropertyAccessExpression; function updatePropertyAccess(node: PropertyAccessExpression, expression: Expression, name: Identifier): PropertyAccessExpression; + function createPropertyAccessChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, name: string | Identifier): PropertyAccessChain; + function updatePropertyAccessChain(node: PropertyAccessChain, expression: Expression, questionDotToken: QuestionDotToken | undefined, name: Identifier): PropertyAccessChain; function createElementAccess(expression: Expression, index: number | Expression): ElementAccessExpression; function updateElementAccess(node: ElementAccessExpression, expression: Expression, argumentExpression: Expression): ElementAccessExpression; + function createElementAccessChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, index: number | Expression): ElementAccessChain; + function updateElementAccessChain(node: ElementAccessChain, expression: Expression, questionDotToken: QuestionDotToken | undefined, argumentExpression: Expression): ElementAccessChain; function createCall(expression: Expression, typeArguments: ReadonlyArray | undefined, argumentsArray: ReadonlyArray | undefined): CallExpression; function updateCall(node: CallExpression, expression: Expression, typeArguments: ReadonlyArray | undefined, argumentsArray: ReadonlyArray): CallExpression; + function createCallChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, typeArguments: ReadonlyArray | undefined, argumentsArray: ReadonlyArray | undefined): CallChain; + function updateCallChain(node: CallChain, expression: Expression, questionDotToken: QuestionDotToken | undefined, typeArguments: ReadonlyArray | undefined, argumentsArray: ReadonlyArray): CallChain; function createNew(expression: Expression, typeArguments: ReadonlyArray | undefined, argumentsArray: ReadonlyArray | undefined): NewExpression; function updateNew(node: NewExpression, expression: Expression, typeArguments: ReadonlyArray | undefined, argumentsArray: ReadonlyArray | undefined): NewExpression; /** @deprecated */ function createTaggedTemplate(tag: Expression, template: TemplateLiteral): TaggedTemplateExpression; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 279357eddf752..72ad9da1b90da 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -104,329 +104,331 @@ declare namespace ts { DotDotDotToken = 25, SemicolonToken = 26, CommaToken = 27, - LessThanToken = 28, - LessThanSlashToken = 29, - GreaterThanToken = 30, - LessThanEqualsToken = 31, - GreaterThanEqualsToken = 32, - EqualsEqualsToken = 33, - ExclamationEqualsToken = 34, - EqualsEqualsEqualsToken = 35, - ExclamationEqualsEqualsToken = 36, - EqualsGreaterThanToken = 37, - PlusToken = 38, - MinusToken = 39, - AsteriskToken = 40, - AsteriskAsteriskToken = 41, - SlashToken = 42, - PercentToken = 43, - PlusPlusToken = 44, - MinusMinusToken = 45, - LessThanLessThanToken = 46, - GreaterThanGreaterThanToken = 47, - GreaterThanGreaterThanGreaterThanToken = 48, - AmpersandToken = 49, - BarToken = 50, - CaretToken = 51, - ExclamationToken = 52, - TildeToken = 53, - AmpersandAmpersandToken = 54, - BarBarToken = 55, - QuestionToken = 56, - ColonToken = 57, - AtToken = 58, + QuestionDotToken = 28, + LessThanToken = 29, + LessThanSlashToken = 30, + GreaterThanToken = 31, + LessThanEqualsToken = 32, + GreaterThanEqualsToken = 33, + EqualsEqualsToken = 34, + ExclamationEqualsToken = 35, + EqualsEqualsEqualsToken = 36, + ExclamationEqualsEqualsToken = 37, + EqualsGreaterThanToken = 38, + PlusToken = 39, + MinusToken = 40, + AsteriskToken = 41, + AsteriskAsteriskToken = 42, + SlashToken = 43, + PercentToken = 44, + PlusPlusToken = 45, + MinusMinusToken = 46, + LessThanLessThanToken = 47, + GreaterThanGreaterThanToken = 48, + GreaterThanGreaterThanGreaterThanToken = 49, + AmpersandToken = 50, + BarToken = 51, + CaretToken = 52, + ExclamationToken = 53, + TildeToken = 54, + AmpersandAmpersandToken = 55, + BarBarToken = 56, + QuestionToken = 57, + ColonToken = 58, + AtToken = 59, /** Only the JSDoc scanner produces BacktickToken. The normal scanner produces NoSubstitutionTemplateLiteral and related kinds. */ - BacktickToken = 59, - EqualsToken = 60, - PlusEqualsToken = 61, - MinusEqualsToken = 62, - AsteriskEqualsToken = 63, - AsteriskAsteriskEqualsToken = 64, - SlashEqualsToken = 65, - PercentEqualsToken = 66, - LessThanLessThanEqualsToken = 67, - GreaterThanGreaterThanEqualsToken = 68, - GreaterThanGreaterThanGreaterThanEqualsToken = 69, - AmpersandEqualsToken = 70, - BarEqualsToken = 71, - CaretEqualsToken = 72, - Identifier = 73, - BreakKeyword = 74, - CaseKeyword = 75, - CatchKeyword = 76, - ClassKeyword = 77, - ConstKeyword = 78, - ContinueKeyword = 79, - DebuggerKeyword = 80, - DefaultKeyword = 81, - DeleteKeyword = 82, - DoKeyword = 83, - ElseKeyword = 84, - EnumKeyword = 85, - ExportKeyword = 86, - ExtendsKeyword = 87, - FalseKeyword = 88, - FinallyKeyword = 89, - ForKeyword = 90, - FunctionKeyword = 91, - IfKeyword = 92, - ImportKeyword = 93, - InKeyword = 94, - InstanceOfKeyword = 95, - NewKeyword = 96, - NullKeyword = 97, - ReturnKeyword = 98, - SuperKeyword = 99, - SwitchKeyword = 100, - ThisKeyword = 101, - ThrowKeyword = 102, - TrueKeyword = 103, - TryKeyword = 104, - TypeOfKeyword = 105, - VarKeyword = 106, - VoidKeyword = 107, - WhileKeyword = 108, - WithKeyword = 109, - ImplementsKeyword = 110, - InterfaceKeyword = 111, - LetKeyword = 112, - PackageKeyword = 113, - PrivateKeyword = 114, - ProtectedKeyword = 115, - PublicKeyword = 116, - StaticKeyword = 117, - YieldKeyword = 118, - AbstractKeyword = 119, - AsKeyword = 120, - AnyKeyword = 121, - AsyncKeyword = 122, - AwaitKeyword = 123, - BooleanKeyword = 124, - ConstructorKeyword = 125, - DeclareKeyword = 126, - GetKeyword = 127, - InferKeyword = 128, - IsKeyword = 129, - KeyOfKeyword = 130, - ModuleKeyword = 131, - NamespaceKeyword = 132, - NeverKeyword = 133, - ReadonlyKeyword = 134, - RequireKeyword = 135, - NumberKeyword = 136, - ObjectKeyword = 137, - SetKeyword = 138, - StringKeyword = 139, - SymbolKeyword = 140, - TypeKeyword = 141, - UndefinedKeyword = 142, - UniqueKeyword = 143, - UnknownKeyword = 144, - FromKeyword = 145, - GlobalKeyword = 146, - BigIntKeyword = 147, - OfKeyword = 148, - QualifiedName = 149, - ComputedPropertyName = 150, - TypeParameter = 151, - Parameter = 152, - Decorator = 153, - PropertySignature = 154, - PropertyDeclaration = 155, - MethodSignature = 156, - MethodDeclaration = 157, - Constructor = 158, - GetAccessor = 159, - SetAccessor = 160, - CallSignature = 161, - ConstructSignature = 162, - IndexSignature = 163, - TypePredicate = 164, - TypeReference = 165, - FunctionType = 166, - ConstructorType = 167, - TypeQuery = 168, - TypeLiteral = 169, - ArrayType = 170, - TupleType = 171, - OptionalType = 172, - RestType = 173, - UnionType = 174, - IntersectionType = 175, - ConditionalType = 176, - InferType = 177, - ParenthesizedType = 178, - ThisType = 179, - TypeOperator = 180, - IndexedAccessType = 181, - MappedType = 182, - LiteralType = 183, - ImportType = 184, - ObjectBindingPattern = 185, - ArrayBindingPattern = 186, - BindingElement = 187, - ArrayLiteralExpression = 188, - ObjectLiteralExpression = 189, - PropertyAccessExpression = 190, - ElementAccessExpression = 191, - CallExpression = 192, - NewExpression = 193, - TaggedTemplateExpression = 194, - TypeAssertionExpression = 195, - ParenthesizedExpression = 196, - FunctionExpression = 197, - ArrowFunction = 198, - DeleteExpression = 199, - TypeOfExpression = 200, - VoidExpression = 201, - AwaitExpression = 202, - PrefixUnaryExpression = 203, - PostfixUnaryExpression = 204, - BinaryExpression = 205, - ConditionalExpression = 206, - TemplateExpression = 207, - YieldExpression = 208, - SpreadElement = 209, - ClassExpression = 210, - OmittedExpression = 211, - ExpressionWithTypeArguments = 212, - AsExpression = 213, - NonNullExpression = 214, - MetaProperty = 215, - SyntheticExpression = 216, - TemplateSpan = 217, - SemicolonClassElement = 218, - Block = 219, - VariableStatement = 220, - EmptyStatement = 221, - ExpressionStatement = 222, - IfStatement = 223, - DoStatement = 224, - WhileStatement = 225, - ForStatement = 226, - ForInStatement = 227, - ForOfStatement = 228, - ContinueStatement = 229, - BreakStatement = 230, - ReturnStatement = 231, - WithStatement = 232, - SwitchStatement = 233, - LabeledStatement = 234, - ThrowStatement = 235, - TryStatement = 236, - DebuggerStatement = 237, - VariableDeclaration = 238, - VariableDeclarationList = 239, - FunctionDeclaration = 240, - ClassDeclaration = 241, - InterfaceDeclaration = 242, - TypeAliasDeclaration = 243, - EnumDeclaration = 244, - ModuleDeclaration = 245, - ModuleBlock = 246, - CaseBlock = 247, - NamespaceExportDeclaration = 248, - ImportEqualsDeclaration = 249, - ImportDeclaration = 250, - ImportClause = 251, - NamespaceImport = 252, - NamedImports = 253, - ImportSpecifier = 254, - ExportAssignment = 255, - ExportDeclaration = 256, - NamedExports = 257, - ExportSpecifier = 258, - MissingDeclaration = 259, - ExternalModuleReference = 260, - JsxElement = 261, - JsxSelfClosingElement = 262, - JsxOpeningElement = 263, - JsxClosingElement = 264, - JsxFragment = 265, - JsxOpeningFragment = 266, - JsxClosingFragment = 267, - JsxAttribute = 268, - JsxAttributes = 269, - JsxSpreadAttribute = 270, - JsxExpression = 271, - CaseClause = 272, - DefaultClause = 273, - HeritageClause = 274, - CatchClause = 275, - PropertyAssignment = 276, - ShorthandPropertyAssignment = 277, - SpreadAssignment = 278, - EnumMember = 279, - UnparsedPrologue = 280, - UnparsedPrepend = 281, - UnparsedText = 282, - UnparsedInternalText = 283, - UnparsedSyntheticReference = 284, - SourceFile = 285, - Bundle = 286, - UnparsedSource = 287, - InputFiles = 288, - JSDocTypeExpression = 289, - JSDocAllType = 290, - JSDocUnknownType = 291, - JSDocNullableType = 292, - JSDocNonNullableType = 293, - JSDocOptionalType = 294, - JSDocFunctionType = 295, - JSDocVariadicType = 296, - JSDocNamepathType = 297, - JSDocComment = 298, - JSDocTypeLiteral = 299, - JSDocSignature = 300, - JSDocTag = 301, - JSDocAugmentsTag = 302, - JSDocAuthorTag = 303, - JSDocClassTag = 304, - JSDocCallbackTag = 305, - JSDocEnumTag = 306, - JSDocParameterTag = 307, - JSDocReturnTag = 308, - JSDocThisTag = 309, - JSDocTypeTag = 310, - JSDocTemplateTag = 311, - JSDocTypedefTag = 312, - JSDocPropertyTag = 313, - SyntaxList = 314, - NotEmittedStatement = 315, - PartiallyEmittedExpression = 316, - CommaListExpression = 317, - MergeDeclarationMarker = 318, - EndOfDeclarationMarker = 319, - Count = 320, - FirstAssignment = 60, - LastAssignment = 72, - FirstCompoundAssignment = 61, - LastCompoundAssignment = 72, - FirstReservedWord = 74, - LastReservedWord = 109, - FirstKeyword = 74, - LastKeyword = 148, - FirstFutureReservedWord = 110, - LastFutureReservedWord = 118, - FirstTypeNode = 164, - LastTypeNode = 184, + BacktickToken = 60, + EqualsToken = 61, + PlusEqualsToken = 62, + MinusEqualsToken = 63, + AsteriskEqualsToken = 64, + AsteriskAsteriskEqualsToken = 65, + SlashEqualsToken = 66, + PercentEqualsToken = 67, + LessThanLessThanEqualsToken = 68, + GreaterThanGreaterThanEqualsToken = 69, + GreaterThanGreaterThanGreaterThanEqualsToken = 70, + AmpersandEqualsToken = 71, + BarEqualsToken = 72, + CaretEqualsToken = 73, + Identifier = 74, + BreakKeyword = 75, + CaseKeyword = 76, + CatchKeyword = 77, + ClassKeyword = 78, + ConstKeyword = 79, + ContinueKeyword = 80, + DebuggerKeyword = 81, + DefaultKeyword = 82, + DeleteKeyword = 83, + DoKeyword = 84, + ElseKeyword = 85, + EnumKeyword = 86, + ExportKeyword = 87, + ExtendsKeyword = 88, + FalseKeyword = 89, + FinallyKeyword = 90, + ForKeyword = 91, + FunctionKeyword = 92, + IfKeyword = 93, + ImportKeyword = 94, + InKeyword = 95, + InstanceOfKeyword = 96, + NewKeyword = 97, + NullKeyword = 98, + ReturnKeyword = 99, + SuperKeyword = 100, + SwitchKeyword = 101, + ThisKeyword = 102, + ThrowKeyword = 103, + TrueKeyword = 104, + TryKeyword = 105, + TypeOfKeyword = 106, + VarKeyword = 107, + VoidKeyword = 108, + WhileKeyword = 109, + WithKeyword = 110, + ImplementsKeyword = 111, + InterfaceKeyword = 112, + LetKeyword = 113, + PackageKeyword = 114, + PrivateKeyword = 115, + ProtectedKeyword = 116, + PublicKeyword = 117, + StaticKeyword = 118, + YieldKeyword = 119, + AbstractKeyword = 120, + AsKeyword = 121, + AnyKeyword = 122, + AsyncKeyword = 123, + AwaitKeyword = 124, + BooleanKeyword = 125, + ConstructorKeyword = 126, + DeclareKeyword = 127, + GetKeyword = 128, + InferKeyword = 129, + IsKeyword = 130, + KeyOfKeyword = 131, + ModuleKeyword = 132, + NamespaceKeyword = 133, + NeverKeyword = 134, + ReadonlyKeyword = 135, + RequireKeyword = 136, + NumberKeyword = 137, + ObjectKeyword = 138, + SetKeyword = 139, + StringKeyword = 140, + SymbolKeyword = 141, + TypeKeyword = 142, + UndefinedKeyword = 143, + UniqueKeyword = 144, + UnknownKeyword = 145, + FromKeyword = 146, + GlobalKeyword = 147, + BigIntKeyword = 148, + OfKeyword = 149, + QualifiedName = 150, + ComputedPropertyName = 151, + TypeParameter = 152, + Parameter = 153, + Decorator = 154, + PropertySignature = 155, + PropertyDeclaration = 156, + MethodSignature = 157, + MethodDeclaration = 158, + Constructor = 159, + GetAccessor = 160, + SetAccessor = 161, + CallSignature = 162, + ConstructSignature = 163, + IndexSignature = 164, + TypePredicate = 165, + TypeReference = 166, + FunctionType = 167, + ConstructorType = 168, + TypeQuery = 169, + TypeLiteral = 170, + ArrayType = 171, + TupleType = 172, + OptionalType = 173, + RestType = 174, + UnionType = 175, + IntersectionType = 176, + ConditionalType = 177, + InferType = 178, + ParenthesizedType = 179, + ThisType = 180, + TypeOperator = 181, + IndexedAccessType = 182, + MappedType = 183, + LiteralType = 184, + ImportType = 185, + ObjectBindingPattern = 186, + ArrayBindingPattern = 187, + BindingElement = 188, + ArrayLiteralExpression = 189, + ObjectLiteralExpression = 190, + PropertyAccessExpression = 191, + ElementAccessExpression = 192, + CallExpression = 193, + NewExpression = 194, + TaggedTemplateExpression = 195, + TypeAssertionExpression = 196, + ParenthesizedExpression = 197, + FunctionExpression = 198, + ArrowFunction = 199, + DeleteExpression = 200, + TypeOfExpression = 201, + VoidExpression = 202, + AwaitExpression = 203, + PrefixUnaryExpression = 204, + PostfixUnaryExpression = 205, + BinaryExpression = 206, + ConditionalExpression = 207, + TemplateExpression = 208, + YieldExpression = 209, + SpreadElement = 210, + ClassExpression = 211, + OmittedExpression = 212, + ExpressionWithTypeArguments = 213, + AsExpression = 214, + NonNullExpression = 215, + MetaProperty = 216, + SyntheticExpression = 217, + TemplateSpan = 218, + SemicolonClassElement = 219, + Block = 220, + VariableStatement = 221, + EmptyStatement = 222, + ExpressionStatement = 223, + IfStatement = 224, + DoStatement = 225, + WhileStatement = 226, + ForStatement = 227, + ForInStatement = 228, + ForOfStatement = 229, + ContinueStatement = 230, + BreakStatement = 231, + ReturnStatement = 232, + WithStatement = 233, + SwitchStatement = 234, + LabeledStatement = 235, + ThrowStatement = 236, + TryStatement = 237, + DebuggerStatement = 238, + VariableDeclaration = 239, + VariableDeclarationList = 240, + FunctionDeclaration = 241, + ClassDeclaration = 242, + InterfaceDeclaration = 243, + TypeAliasDeclaration = 244, + EnumDeclaration = 245, + ModuleDeclaration = 246, + ModuleBlock = 247, + CaseBlock = 248, + NamespaceExportDeclaration = 249, + ImportEqualsDeclaration = 250, + ImportDeclaration = 251, + ImportClause = 252, + NamespaceImport = 253, + NamedImports = 254, + ImportSpecifier = 255, + ExportAssignment = 256, + ExportDeclaration = 257, + NamedExports = 258, + ExportSpecifier = 259, + MissingDeclaration = 260, + ExternalModuleReference = 261, + JsxElement = 262, + JsxSelfClosingElement = 263, + JsxOpeningElement = 264, + JsxClosingElement = 265, + JsxFragment = 266, + JsxOpeningFragment = 267, + JsxClosingFragment = 268, + JsxAttribute = 269, + JsxAttributes = 270, + JsxSpreadAttribute = 271, + JsxExpression = 272, + CaseClause = 273, + DefaultClause = 274, + HeritageClause = 275, + CatchClause = 276, + PropertyAssignment = 277, + ShorthandPropertyAssignment = 278, + SpreadAssignment = 279, + EnumMember = 280, + UnparsedPrologue = 281, + UnparsedPrepend = 282, + UnparsedText = 283, + UnparsedInternalText = 284, + UnparsedSyntheticReference = 285, + SourceFile = 286, + Bundle = 287, + UnparsedSource = 288, + InputFiles = 289, + JSDocTypeExpression = 290, + JSDocAllType = 291, + JSDocUnknownType = 292, + JSDocNullableType = 293, + JSDocNonNullableType = 294, + JSDocOptionalType = 295, + JSDocFunctionType = 296, + JSDocVariadicType = 297, + JSDocNamepathType = 298, + JSDocComment = 299, + JSDocTypeLiteral = 300, + JSDocSignature = 301, + JSDocTag = 302, + JSDocAugmentsTag = 303, + JSDocAuthorTag = 304, + JSDocClassTag = 305, + JSDocCallbackTag = 306, + JSDocEnumTag = 307, + JSDocParameterTag = 308, + JSDocReturnTag = 309, + JSDocThisTag = 310, + JSDocTypeTag = 311, + JSDocTemplateTag = 312, + JSDocTypedefTag = 313, + JSDocPropertyTag = 314, + SyntaxList = 315, + NotEmittedStatement = 316, + PartiallyEmittedExpression = 317, + CommaListExpression = 318, + MergeDeclarationMarker = 319, + EndOfDeclarationMarker = 320, + SyntheticReferenceExpression = 321, + Count = 322, + FirstAssignment = 61, + LastAssignment = 73, + FirstCompoundAssignment = 62, + LastCompoundAssignment = 73, + FirstReservedWord = 75, + LastReservedWord = 110, + FirstKeyword = 75, + LastKeyword = 149, + FirstFutureReservedWord = 111, + LastFutureReservedWord = 119, + FirstTypeNode = 165, + LastTypeNode = 185, FirstPunctuation = 18, - LastPunctuation = 72, + LastPunctuation = 73, FirstToken = 0, - LastToken = 148, + LastToken = 149, FirstTriviaToken = 2, LastTriviaToken = 7, FirstLiteralToken = 8, LastLiteralToken = 14, FirstTemplateToken = 14, LastTemplateToken = 17, - FirstBinaryOperator = 28, - LastBinaryOperator = 72, - FirstNode = 149, - FirstJSDocNode = 289, - LastJSDocNode = 313, - FirstJSDocTagNode = 301, - LastJSDocTagNode = 313, + FirstBinaryOperator = 29, + LastBinaryOperator = 73, + FirstNode = 150, + FirstJSDocNode = 290, + LastJSDocNode = 314, + FirstJSDocTagNode = 302, + LastJSDocTagNode = 314, } export enum NodeFlags { None = 0, @@ -435,27 +437,28 @@ declare namespace ts { NestedNamespace = 4, Synthesized = 8, Namespace = 16, - ExportContext = 32, - ContainsThis = 64, - HasImplicitReturn = 128, - HasExplicitReturn = 256, - GlobalAugmentation = 512, - HasAsyncFunctions = 1024, - DisallowInContext = 2048, - YieldContext = 4096, - DecoratorContext = 8192, - AwaitContext = 16384, - ThisNodeHasError = 32768, - JavaScriptFile = 65536, - ThisNodeOrAnySubNodesHasError = 131072, - HasAggregatedChildData = 262144, - JSDoc = 2097152, - JsonFile = 16777216, + OptionalChain = 32, + ExportContext = 64, + ContainsThis = 128, + HasImplicitReturn = 256, + HasExplicitReturn = 512, + GlobalAugmentation = 1024, + HasAsyncFunctions = 2048, + DisallowInContext = 4096, + YieldContext = 8192, + DecoratorContext = 16384, + AwaitContext = 32768, + ThisNodeHasError = 65536, + JavaScriptFile = 131072, + ThisNodeOrAnySubNodesHasError = 262144, + HasAggregatedChildData = 524288, + JSDoc = 4194304, + JsonFile = 33554432, BlockScoped = 3, - ReachabilityCheckFlags = 384, - ReachabilityAndEmitFlags = 1408, - ContextFlags = 12679168, - TypeExcludesFlags = 20480, + ReachabilityCheckFlags = 768, + ReachabilityAndEmitFlags = 2816, + ContextFlags = 25358336, + TypeExcludesFlags = 40960, } export enum ModifierFlags { None = 0, @@ -505,8 +508,10 @@ declare namespace ts { export interface Token extends Node { kind: TKind; } + export type DotToken = Token; export type DotDotDotToken = Token; export type QuestionToken = Token; + export type QuestionDotToken = Token; export type ExclamationToken = Token; export type ColonToken = Token; export type EqualsToken = Token; @@ -1077,8 +1082,13 @@ declare namespace ts { export interface PropertyAccessExpression extends MemberExpression, NamedDeclaration { kind: SyntaxKind.PropertyAccessExpression; expression: LeftHandSideExpression; + questionDotToken?: QuestionDotToken; name: Identifier; } + export interface PropertyAccessChain extends PropertyAccessExpression { + _optionalChainBrand: any; + kind: SyntaxKind.PropertyAccessExpression; + } export interface SuperPropertyAccessExpression extends PropertyAccessExpression { expression: SuperExpression; } @@ -1090,8 +1100,13 @@ declare namespace ts { export interface ElementAccessExpression extends MemberExpression { kind: SyntaxKind.ElementAccessExpression; expression: LeftHandSideExpression; + questionDotToken?: QuestionDotToken; argumentExpression: Expression; } + export interface ElementAccessChain extends ElementAccessExpression { + _optionalChainBrand: any; + kind: SyntaxKind.ElementAccessExpression; + } export interface SuperElementAccessExpression extends ElementAccessExpression { expression: SuperExpression; } @@ -1099,9 +1114,15 @@ declare namespace ts { export interface CallExpression extends LeftHandSideExpression, Declaration { kind: SyntaxKind.CallExpression; expression: LeftHandSideExpression; + questionDotToken?: QuestionDotToken; typeArguments?: NodeArray; arguments: NodeArray; } + export interface CallChain extends CallExpression { + _optionalChainBrand: any; + kind: SyntaxKind.CallExpression; + } + export type OptionalChain = PropertyAccessChain | ElementAccessChain | CallChain; export interface SuperCall extends CallExpression { expression: SuperExpression; } @@ -3445,8 +3466,12 @@ declare namespace ts { function isArrayLiteralExpression(node: Node): node is ArrayLiteralExpression; function isObjectLiteralExpression(node: Node): node is ObjectLiteralExpression; function isPropertyAccessExpression(node: Node): node is PropertyAccessExpression; + function isPropertyAccessChain(node: Node): node is PropertyAccessChain; function isElementAccessExpression(node: Node): node is ElementAccessExpression; + function isElementAccessChain(node: Node): node is ElementAccessChain; function isCallExpression(node: Node): node is CallExpression; + function isCallChain(node: Node): node is CallChain; + function isOptionalChain(node: Node): node is PropertyAccessChain | ElementAccessChain | CallChain; function isNewExpression(node: Node): node is NewExpression; function isTaggedTemplateExpression(node: Node): node is TaggedTemplateExpression; function isTypeAssertion(node: Node): node is TypeAssertion; @@ -3890,10 +3915,16 @@ declare namespace ts { function updateObjectLiteral(node: ObjectLiteralExpression, properties: ReadonlyArray): ObjectLiteralExpression; function createPropertyAccess(expression: Expression, name: string | Identifier): PropertyAccessExpression; function updatePropertyAccess(node: PropertyAccessExpression, expression: Expression, name: Identifier): PropertyAccessExpression; + function createPropertyAccessChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, name: string | Identifier): PropertyAccessChain; + function updatePropertyAccessChain(node: PropertyAccessChain, expression: Expression, questionDotToken: QuestionDotToken | undefined, name: Identifier): PropertyAccessChain; function createElementAccess(expression: Expression, index: number | Expression): ElementAccessExpression; function updateElementAccess(node: ElementAccessExpression, expression: Expression, argumentExpression: Expression): ElementAccessExpression; + function createElementAccessChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, index: number | Expression): ElementAccessChain; + function updateElementAccessChain(node: ElementAccessChain, expression: Expression, questionDotToken: QuestionDotToken | undefined, argumentExpression: Expression): ElementAccessChain; function createCall(expression: Expression, typeArguments: ReadonlyArray | undefined, argumentsArray: ReadonlyArray | undefined): CallExpression; function updateCall(node: CallExpression, expression: Expression, typeArguments: ReadonlyArray | undefined, argumentsArray: ReadonlyArray): CallExpression; + function createCallChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, typeArguments: ReadonlyArray | undefined, argumentsArray: ReadonlyArray | undefined): CallChain; + function updateCallChain(node: CallChain, expression: Expression, questionDotToken: QuestionDotToken | undefined, typeArguments: ReadonlyArray | undefined, argumentsArray: ReadonlyArray): CallChain; function createNew(expression: Expression, typeArguments: ReadonlyArray | undefined, argumentsArray: ReadonlyArray | undefined): NewExpression; function updateNew(node: NewExpression, expression: Expression, typeArguments: ReadonlyArray | undefined, argumentsArray: ReadonlyArray | undefined): NewExpression; /** @deprecated */ function createTaggedTemplate(tag: Expression, template: TemplateLiteral): TaggedTemplateExpression; diff --git a/tests/baselines/reference/callChain.js b/tests/baselines/reference/callChain.js new file mode 100644 index 0000000000000..dde51814f9b8b --- /dev/null +++ b/tests/baselines/reference/callChain.js @@ -0,0 +1,17 @@ +//// [callChain.ts] +declare const o1: undefined | (() => number); +o1?.(); + +declare const o2: undefined | { b: () => number }; +o2?.b(); + +declare const o3: { b: (() => { c: string }) | undefined }; +o3.b?.().c; + + +//// [callChain.js] +"use strict"; +var _a, _b, _c, _d; +(_a = o1) === null || _a === void 0 ? void 0 : _a(); +(_b = o2) === null || _b === void 0 ? void 0 : _b.b(); +(_d = (_c = o3).b) === null || _d === void 0 ? void 0 : _d.call(_c).c; diff --git a/tests/baselines/reference/callChain.symbols b/tests/baselines/reference/callChain.symbols new file mode 100644 index 0000000000000..b8d537402e7ac --- /dev/null +++ b/tests/baselines/reference/callChain.symbols @@ -0,0 +1,28 @@ +=== tests/cases/conformance/expressions/optionalChaining/callChain/callChain.ts === +declare const o1: undefined | (() => number); +>o1 : Symbol(o1, Decl(callChain.ts, 0, 13)) + +o1?.(); +>o1 : Symbol(o1, Decl(callChain.ts, 0, 13)) + +declare const o2: undefined | { b: () => number }; +>o2 : Symbol(o2, Decl(callChain.ts, 3, 13)) +>b : Symbol(b, Decl(callChain.ts, 3, 31)) + +o2?.b(); +>o2?.b : Symbol(b, Decl(callChain.ts, 3, 31)) +>o2 : Symbol(o2, Decl(callChain.ts, 3, 13)) +>b : Symbol(b, Decl(callChain.ts, 3, 31)) + +declare const o3: { b: (() => { c: string }) | undefined }; +>o3 : Symbol(o3, Decl(callChain.ts, 6, 13)) +>b : Symbol(b, Decl(callChain.ts, 6, 19)) +>c : Symbol(c, Decl(callChain.ts, 6, 31)) + +o3.b?.().c; +>o3.b?.().c : Symbol(c, Decl(callChain.ts, 6, 31)) +>o3.b : Symbol(b, Decl(callChain.ts, 6, 19)) +>o3 : Symbol(o3, Decl(callChain.ts, 6, 13)) +>b : Symbol(b, Decl(callChain.ts, 6, 19)) +>c : Symbol(c, Decl(callChain.ts, 6, 31)) + diff --git a/tests/baselines/reference/callChain.types b/tests/baselines/reference/callChain.types new file mode 100644 index 0000000000000..ba476114311c7 --- /dev/null +++ b/tests/baselines/reference/callChain.types @@ -0,0 +1,31 @@ +=== tests/cases/conformance/expressions/optionalChaining/callChain/callChain.ts === +declare const o1: undefined | (() => number); +>o1 : (() => number) | undefined + +o1?.(); +>o1?.() : number | undefined +>o1 : (() => number) | undefined + +declare const o2: undefined | { b: () => number }; +>o2 : { b: () => number; } | undefined +>b : () => number + +o2?.b(); +>o2?.b() : number | undefined +>o2?.b : (() => number) | undefined +>o2 : { b: () => number; } | undefined +>b : (() => number) | undefined + +declare const o3: { b: (() => { c: string }) | undefined }; +>o3 : { b: (() => { c: string; }) | undefined; } +>b : (() => { c: string; }) | undefined +>c : string + +o3.b?.().c; +>o3.b?.().c : string | undefined +>o3.b?.() : { c: string; } | undefined +>o3.b : (() => { c: string; }) | undefined +>o3 : { b: (() => { c: string; }) | undefined; } +>b : (() => { c: string; }) | undefined +>c : string | undefined + diff --git a/tests/baselines/reference/elementAccessChain.js b/tests/baselines/reference/elementAccessChain.js new file mode 100644 index 0000000000000..c6b4de76afa07 --- /dev/null +++ b/tests/baselines/reference/elementAccessChain.js @@ -0,0 +1,21 @@ +//// [elementAccessChain.ts] +declare const o1: undefined | { b: string }; +o1?.["b"]; + +declare const o2: undefined | { b: { c: string } }; +o2?.["b"].c; +o2?.b["c"]; + +declare const o3: { b: undefined | { c: string } }; +o3["b"]?.c; +o3.b?.["c"]; + + +//// [elementAccessChain.js] +"use strict"; +var _a, _b, _c, _d, _e; +(_a = o1) === null || _a === void 0 ? void 0 : _a["b"]; +(_b = o2) === null || _b === void 0 ? void 0 : _b["b"].c; +(_c = o2) === null || _c === void 0 ? void 0 : _c.b["c"]; +(_d = o3["b"]) === null || _d === void 0 ? void 0 : _d.c; +(_e = o3.b) === null || _e === void 0 ? void 0 : _e["c"]; diff --git a/tests/baselines/reference/elementAccessChain.symbols b/tests/baselines/reference/elementAccessChain.symbols new file mode 100644 index 0000000000000..1a5d7cf975312 --- /dev/null +++ b/tests/baselines/reference/elementAccessChain.symbols @@ -0,0 +1,39 @@ +=== tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.ts === +declare const o1: undefined | { b: string }; +>o1 : Symbol(o1, Decl(elementAccessChain.ts, 0, 13)) +>b : Symbol(b, Decl(elementAccessChain.ts, 0, 31)) + +o1?.["b"]; +>o1 : Symbol(o1, Decl(elementAccessChain.ts, 0, 13)) + +declare const o2: undefined | { b: { c: string } }; +>o2 : Symbol(o2, Decl(elementAccessChain.ts, 3, 13)) +>b : Symbol(b, Decl(elementAccessChain.ts, 3, 31)) +>c : Symbol(c, Decl(elementAccessChain.ts, 3, 36)) + +o2?.["b"].c; +>o2?.["b"].c : Symbol(c, Decl(elementAccessChain.ts, 3, 36)) +>o2 : Symbol(o2, Decl(elementAccessChain.ts, 3, 13)) +>c : Symbol(c, Decl(elementAccessChain.ts, 3, 36)) + +o2?.b["c"]; +>o2?.b : Symbol(b, Decl(elementAccessChain.ts, 3, 31)) +>o2 : Symbol(o2, Decl(elementAccessChain.ts, 3, 13)) +>b : Symbol(b, Decl(elementAccessChain.ts, 3, 31)) + +declare const o3: { b: undefined | { c: string } }; +>o3 : Symbol(o3, Decl(elementAccessChain.ts, 7, 13)) +>b : Symbol(b, Decl(elementAccessChain.ts, 7, 19)) +>c : Symbol(c, Decl(elementAccessChain.ts, 7, 36)) + +o3["b"]?.c; +>o3["b"]?.c : Symbol(c, Decl(elementAccessChain.ts, 7, 36)) +>o3 : Symbol(o3, Decl(elementAccessChain.ts, 7, 13)) +>"b" : Symbol(b, Decl(elementAccessChain.ts, 7, 19)) +>c : Symbol(c, Decl(elementAccessChain.ts, 7, 36)) + +o3.b?.["c"]; +>o3.b : Symbol(b, Decl(elementAccessChain.ts, 7, 19)) +>o3 : Symbol(o3, Decl(elementAccessChain.ts, 7, 13)) +>b : Symbol(b, Decl(elementAccessChain.ts, 7, 19)) + diff --git a/tests/baselines/reference/elementAccessChain.types b/tests/baselines/reference/elementAccessChain.types new file mode 100644 index 0000000000000..b93246f71c01e --- /dev/null +++ b/tests/baselines/reference/elementAccessChain.types @@ -0,0 +1,48 @@ +=== tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.ts === +declare const o1: undefined | { b: string }; +>o1 : { b: string; } | undefined +>b : string + +o1?.["b"]; +>o1?.["b"] : string | undefined +>o1 : { b: string; } | undefined +>"b" : "b" + +declare const o2: undefined | { b: { c: string } }; +>o2 : { b: { c: string; }; } | undefined +>b : { c: string; } +>c : string + +o2?.["b"].c; +>o2?.["b"].c : string | undefined +>o2?.["b"] : { c: string; } | undefined +>o2 : { b: { c: string; }; } | undefined +>"b" : "b" +>c : string | undefined + +o2?.b["c"]; +>o2?.b["c"] : string | undefined +>o2?.b : { c: string; } | undefined +>o2 : { b: { c: string; }; } | undefined +>b : { c: string; } | undefined +>"c" : "c" + +declare const o3: { b: undefined | { c: string } }; +>o3 : { b: { c: string; } | undefined; } +>b : { c: string; } | undefined +>c : string + +o3["b"]?.c; +>o3["b"]?.c : string | undefined +>o3["b"] : { c: string; } | undefined +>o3 : { b: { c: string; } | undefined; } +>"b" : "b" +>c : string | undefined + +o3.b?.["c"]; +>o3.b?.["c"] : string | undefined +>o3.b : { c: string; } | undefined +>o3 : { b: { c: string; } | undefined; } +>b : { c: string; } | undefined +>"c" : "c" + diff --git a/tests/baselines/reference/propertyAccessChain.js b/tests/baselines/reference/propertyAccessChain.js new file mode 100644 index 0000000000000..f9ea8a78f26ba --- /dev/null +++ b/tests/baselines/reference/propertyAccessChain.js @@ -0,0 +1,17 @@ +//// [propertyAccessChain.ts] +declare const o1: undefined | { b: string }; +o1?.b; + +declare const o2: undefined | { b: { c: string } }; +o2?.b.c; + +declare const o3: { b: undefined | { c: string } }; +o3.b?.c; + + +//// [propertyAccessChain.js] +"use strict"; +var _a, _b, _c; +(_a = o1) === null || _a === void 0 ? void 0 : _a.b; +(_b = o2) === null || _b === void 0 ? void 0 : _b.b.c; +(_c = o3.b) === null || _c === void 0 ? void 0 : _c.c; diff --git a/tests/baselines/reference/propertyAccessChain.symbols b/tests/baselines/reference/propertyAccessChain.symbols new file mode 100644 index 0000000000000..384ac9d9f05b9 --- /dev/null +++ b/tests/baselines/reference/propertyAccessChain.symbols @@ -0,0 +1,34 @@ +=== tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.ts === +declare const o1: undefined | { b: string }; +>o1 : Symbol(o1, Decl(propertyAccessChain.ts, 0, 13)) +>b : Symbol(b, Decl(propertyAccessChain.ts, 0, 31)) + +o1?.b; +>o1?.b : Symbol(b, Decl(propertyAccessChain.ts, 0, 31)) +>o1 : Symbol(o1, Decl(propertyAccessChain.ts, 0, 13)) +>b : Symbol(b, Decl(propertyAccessChain.ts, 0, 31)) + +declare const o2: undefined | { b: { c: string } }; +>o2 : Symbol(o2, Decl(propertyAccessChain.ts, 3, 13)) +>b : Symbol(b, Decl(propertyAccessChain.ts, 3, 31)) +>c : Symbol(c, Decl(propertyAccessChain.ts, 3, 36)) + +o2?.b.c; +>o2?.b.c : Symbol(c, Decl(propertyAccessChain.ts, 3, 36)) +>o2?.b : Symbol(b, Decl(propertyAccessChain.ts, 3, 31)) +>o2 : Symbol(o2, Decl(propertyAccessChain.ts, 3, 13)) +>b : Symbol(b, Decl(propertyAccessChain.ts, 3, 31)) +>c : Symbol(c, Decl(propertyAccessChain.ts, 3, 36)) + +declare const o3: { b: undefined | { c: string } }; +>o3 : Symbol(o3, Decl(propertyAccessChain.ts, 6, 13)) +>b : Symbol(b, Decl(propertyAccessChain.ts, 6, 19)) +>c : Symbol(c, Decl(propertyAccessChain.ts, 6, 36)) + +o3.b?.c; +>o3.b?.c : Symbol(c, Decl(propertyAccessChain.ts, 6, 36)) +>o3.b : Symbol(b, Decl(propertyAccessChain.ts, 6, 19)) +>o3 : Symbol(o3, Decl(propertyAccessChain.ts, 6, 13)) +>b : Symbol(b, Decl(propertyAccessChain.ts, 6, 19)) +>c : Symbol(c, Decl(propertyAccessChain.ts, 6, 36)) + diff --git a/tests/baselines/reference/propertyAccessChain.types b/tests/baselines/reference/propertyAccessChain.types new file mode 100644 index 0000000000000..42122303839de --- /dev/null +++ b/tests/baselines/reference/propertyAccessChain.types @@ -0,0 +1,34 @@ +=== tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.ts === +declare const o1: undefined | { b: string }; +>o1 : { b: string; } | undefined +>b : string + +o1?.b; +>o1?.b : string | undefined +>o1 : { b: string; } | undefined +>b : string | undefined + +declare const o2: undefined | { b: { c: string } }; +>o2 : { b: { c: string; }; } | undefined +>b : { c: string; } +>c : string + +o2?.b.c; +>o2?.b.c : string | undefined +>o2?.b : { c: string; } | undefined +>o2 : { b: { c: string; }; } | undefined +>b : { c: string; } | undefined +>c : string | undefined + +declare const o3: { b: undefined | { c: string } }; +>o3 : { b: { c: string; } | undefined; } +>b : { c: string; } | undefined +>c : string + +o3.b?.c; +>o3.b?.c : string | undefined +>o3.b : { c: string; } | undefined +>o3 : { b: { c: string; } | undefined; } +>b : { c: string; } | undefined +>c : string | undefined + diff --git a/tests/cases/conformance/expressions/optionalChaining/callChain/callChain.ts b/tests/cases/conformance/expressions/optionalChaining/callChain/callChain.ts new file mode 100644 index 0000000000000..1e239f70628cd --- /dev/null +++ b/tests/cases/conformance/expressions/optionalChaining/callChain/callChain.ts @@ -0,0 +1,10 @@ +// @strict: true + +declare const o1: undefined | (() => number); +o1?.(); + +declare const o2: undefined | { b: () => number }; +o2?.b(); + +declare const o3: { b: (() => { c: string }) | undefined }; +o3.b?.().c; diff --git a/tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.ts b/tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.ts new file mode 100644 index 0000000000000..b1a9efe746d4a --- /dev/null +++ b/tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.ts @@ -0,0 +1,12 @@ +// @strict: true + +declare const o1: undefined | { b: string }; +o1?.["b"]; + +declare const o2: undefined | { b: { c: string } }; +o2?.["b"].c; +o2?.b["c"]; + +declare const o3: { b: undefined | { c: string } }; +o3["b"]?.c; +o3.b?.["c"]; diff --git a/tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.ts b/tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.ts new file mode 100644 index 0000000000000..0514a8e1880c9 --- /dev/null +++ b/tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.ts @@ -0,0 +1,10 @@ +// @strict: true + +declare const o1: undefined | { b: string }; +o1?.b; + +declare const o2: undefined | { b: { c: string } }; +o2?.b.c; + +declare const o3: { b: undefined | { c: string } }; +o3.b?.c; diff --git a/tests/cases/fourslash/signatureHelpOptionalCall.ts b/tests/cases/fourslash/signatureHelpOptionalCall.ts new file mode 100644 index 0000000000000..fea90d7ee1f2a --- /dev/null +++ b/tests/cases/fourslash/signatureHelpOptionalCall.ts @@ -0,0 +1,14 @@ +/// + +////function fnTest(str: string, num: number) { } +////fnTest?.(/*1*/); + +verify.signatureHelp( + { + marker: "1", + text: 'fnTest(str: string, num: number): void', + parameterCount: 2, + parameterName: "str", + parameterSpan: "str: string", + }, +); From c2f53fcc768452ffcad3076acdcf927b0473bfe7 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Sat, 7 Sep 2019 13:53:09 -0700 Subject: [PATCH 02/19] Add grammar error for invalid tagged template, more tests --- src/compiler/checker.ts | 9 +- src/compiler/diagnosticMessages.json | 4 + src/compiler/parser.ts | 2 +- src/compiler/transformers/esnext.ts | 1 - src/testRunner/tsconfig.json | 1 + .../unittests/evaluation/optionalCall.ts | 191 ++++++++++++++++++ tests/baselines/reference/callChain.2.js | 16 ++ tests/baselines/reference/callChain.2.symbols | 28 +++ tests/baselines/reference/callChain.2.types | 31 +++ .../reference/elementAccessChain.2.js | 20 ++ .../reference/elementAccessChain.2.symbols | 43 ++++ .../reference/elementAccessChain.2.types | 48 +++++ .../reference/propertyAccessChain.2.js | 16 ++ .../reference/propertyAccessChain.2.symbols | 34 ++++ .../reference/propertyAccessChain.2.types | 34 ++++ .../reference/taggedTemplateChain.errors.txt | 13 ++ .../reference/taggedTemplateChain.js | 13 ++ .../reference/taggedTemplateChain.symbols | 10 + .../reference/taggedTemplateChain.types | 15 ++ .../optionalChaining/callChain/callChain.2.ts | 10 + .../elementAccessChain.2.ts | 12 ++ .../propertyAccessChain.2.ts | 10 + .../taggedTemplateChain.ts | 4 + 23 files changed, 562 insertions(+), 3 deletions(-) create mode 100644 src/testRunner/unittests/evaluation/optionalCall.ts create mode 100644 tests/baselines/reference/callChain.2.js create mode 100644 tests/baselines/reference/callChain.2.symbols create mode 100644 tests/baselines/reference/callChain.2.types create mode 100644 tests/baselines/reference/elementAccessChain.2.js create mode 100644 tests/baselines/reference/elementAccessChain.2.symbols create mode 100644 tests/baselines/reference/elementAccessChain.2.types create mode 100644 tests/baselines/reference/propertyAccessChain.2.js create mode 100644 tests/baselines/reference/propertyAccessChain.2.symbols create mode 100644 tests/baselines/reference/propertyAccessChain.2.types create mode 100644 tests/baselines/reference/taggedTemplateChain.errors.txt create mode 100644 tests/baselines/reference/taggedTemplateChain.js create mode 100644 tests/baselines/reference/taggedTemplateChain.symbols create mode 100644 tests/baselines/reference/taggedTemplateChain.types create mode 100644 tests/cases/conformance/expressions/optionalChaining/callChain/callChain.2.ts create mode 100644 tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.2.ts create mode 100644 tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.2.ts create mode 100644 tests/cases/conformance/expressions/optionalChaining/taggedTemplateChain/taggedTemplateChain.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5c81e13d92cad..25adadc34b87a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -23182,7 +23182,7 @@ namespace ts { } function checkTaggedTemplateExpression(node: TaggedTemplateExpression): Type { - checkGrammarTypeArguments(node, node.typeArguments); + if (!checkGrammarTaggedTemplateChain(node)) checkGrammarTypeArguments(node, node.typeArguments); if (languageVersion < ScriptTarget.ES2015) { checkExternalEmitHelpers(node, ExternalEmitHelpers.MakeTemplateObject); } @@ -32891,6 +32891,13 @@ namespace ts { checkGrammarForAtLeastOneTypeArgument(node, typeArguments); } + function checkGrammarTaggedTemplateChain(node: TaggedTemplateExpression): boolean { + if (node.questionDotToken || node.flags & NodeFlags.OptionalChain) { + return grammarErrorOnNode(node.template, Diagnostics.Tagged_template_expressions_are_not_permitted_in_an_optional_chain); + } + return false; + } + function checkGrammarForOmittedArgument(args: NodeArray | undefined): boolean { if (args) { for (const arg of args) { diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index b296c224d089d..93763804cf764 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1035,6 +1035,10 @@ "category": "Error", "code": 1356 }, + "Tagged template expressions are not permitted in an optional chain.": { + "category": "Error", + "code": 1357 + }, "Duplicate identifier '{0}'.": { "category": "Error", diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index d427de81dd9f4..23479907c87f4 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -4630,7 +4630,7 @@ namespace ts { let isPropertyAccess = false; if (allowOptionalChain && isStartOfOptionalPropertyOrElementAccessChain()) { questionDotToken = parseExpectedToken(SyntaxKind.QuestionDotToken); - isPropertyAccess = token() !== SyntaxKind.OpenBracketToken; + isPropertyAccess = tokenIsIdentifierOrKeyword(token()); } else { isPropertyAccess = parseOptional(SyntaxKind.DotToken); diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index c2693fb1a4aaf..c51ad4119d297 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -23,7 +23,6 @@ namespace ts { case SyntaxKind.PropertyAccessExpression: case SyntaxKind.ElementAccessExpression: case SyntaxKind.CallExpression: - case SyntaxKind.TaggedTemplateExpression: if (node.flags & NodeFlags.OptionalChain) { const updated = visitOptionalExpression(node as OptionalChain, /*captureThisArg*/ false); Debug.assertNotNode(updated, isSyntheticReference); diff --git a/src/testRunner/tsconfig.json b/src/testRunner/tsconfig.json index db9e96e1ad2fc..a324993494db0 100644 --- a/src/testRunner/tsconfig.json +++ b/src/testRunner/tsconfig.json @@ -75,6 +75,7 @@ "unittests/evaluation/awaiter.ts", "unittests/evaluation/forAwaitOf.ts", "unittests/evaluation/forOf.ts", + "unittests/evaluation/optionalCall.ts", "unittests/evaluation/objectRest.ts", "unittests/services/cancellableLanguageServiceOperations.ts", "unittests/services/colorization.ts", diff --git a/src/testRunner/unittests/evaluation/optionalCall.ts b/src/testRunner/unittests/evaluation/optionalCall.ts new file mode 100644 index 0000000000000..d6317ad0a8ca9 --- /dev/null +++ b/src/testRunner/unittests/evaluation/optionalCall.ts @@ -0,0 +1,191 @@ +describe("unittests:: evaluation:: optionalCall", () => { + it("f?.()", async () => { + const result = evaluator.evaluateTypeScript(` + function f(a) { + output.push(a); + output.push(this); + } + export const output: any[] = []; + f?.(1); + `); + assert.strictEqual(result.output[0], 1); + assert.isUndefined(result.output[1]); + }); + it("o.f?.()", async () => { + const result = evaluator.evaluateTypeScript(` + export const o = { + f(a) { + output.push(a); + output.push(this); + } + }; + export const output: any[] = []; + o.f?.(1); + `); + assert.strictEqual(result.output[0], 1); + assert.strictEqual(result.output[1], result.o); + }); + it("o.x.f?.()", async () => { + const result = evaluator.evaluateTypeScript(` + export const o = { + x: { + f(a) { + output.push(a); + output.push(this); + } + } + }; + export const output: any[] = []; + o.x.f?.(1); + `); + assert.strictEqual(result.output[0], 1); + assert.strictEqual(result.output[1], result.o.x); + }); + it("o?.f()", async () => { + const result = evaluator.evaluateTypeScript(` + export const o = { + f(a) { + output.push(a); + output.push(this); + } + }; + export const output: any[] = []; + o?.f(1); + `); + assert.strictEqual(result.output[0], 1); + assert.strictEqual(result.output[1], result.o); + }); + it("o?.f?.()", async () => { + const result = evaluator.evaluateTypeScript(` + export const o = { + f(a) { + output.push(a); + output.push(this); + } + }; + export const output: any[] = []; + o?.f?.(1); + `); + assert.strictEqual(result.output[0], 1); + assert.strictEqual(result.output[1], result.o); + }); + it("o.x?.f()", async () => { + const result = evaluator.evaluateTypeScript(` + export const o = { + x: { + f(a) { + output.push(a); + output.push(this); + } + } + }; + export const output: any[] = []; + o.x?.f(1); + `); + assert.strictEqual(result.output[0], 1); + assert.strictEqual(result.output[1], result.o.x); + }); + it("o?.x.f()", async () => { + const result = evaluator.evaluateTypeScript(` + export const o = { + x: { + f(a) { + output.push(a); + output.push(this); + } + } + }; + export const output: any[] = []; + o?.x.f(1); + `); + assert.strictEqual(result.output[0], 1); + assert.strictEqual(result.output[1], result.o.x); + }); + it("o?.x?.f()", async () => { + const result = evaluator.evaluateTypeScript(` + export const o = { + x: { + f(a) { + output.push(a); + output.push(this); + } + } + }; + export const output: any[] = []; + o?.x?.f(1); + `); + assert.strictEqual(result.output[0], 1); + assert.strictEqual(result.output[1], result.o.x); + }); + it("o?.x?.f?.()", async () => { + const result = evaluator.evaluateTypeScript(` + export const o = { + x: { + f(a) { + output.push(a); + output.push(this); + } + } + }; + export const output: any[] = []; + o?.x?.f?.(1); + `); + assert.strictEqual(result.output[0], 1); + assert.strictEqual(result.output[1], result.o.x); + }); + it("f?.()?.()", async () => { + const result = evaluator.evaluateTypeScript(` + function g(a) { + output.push(a); + output.push(this); + } + function f(a) { + output.push(a); + return g; + } + export const output: any[] = []; + f?.(1)?.(2) + `); + assert.strictEqual(result.output[0], 1); + assert.strictEqual(result.output[1], 2); + assert.isUndefined(result.output[2]); + }); + it("f?.().f?.()", async () => { + const result = evaluator.evaluateTypeScript(` + export const o = { + f(a) { + output.push(a); + output.push(this); + } + }; + function f(a) { + output.push(a); + return o; + } + export const output: any[] = []; + f?.(1).f?.(2) + `); + assert.strictEqual(result.output[0], 1); + assert.strictEqual(result.output[1], 2); + assert.strictEqual(result.output[2], result.o); + }); + it("f?.()?.f?.()", async () => { + const result = evaluator.evaluateTypeScript(` + export const o = { + f(a) { + output.push(a); + output.push(this); + } + }; + function f(a) { + output.push(a); + return o; + } + export const output: any[] = []; + f?.(1)?.f?.(2) + `); + assert.strictEqual(result.output[0], 1); + assert.strictEqual(result.output[1], 2); + assert.strictEqual(result.output[2], result.o); + }); +}); diff --git a/tests/baselines/reference/callChain.2.js b/tests/baselines/reference/callChain.2.js new file mode 100644 index 0000000000000..a44115ef91673 --- /dev/null +++ b/tests/baselines/reference/callChain.2.js @@ -0,0 +1,16 @@ +//// [callChain.2.ts] +declare const o1: undefined | (() => number); +o1?.(); + +declare const o2: undefined | { b: () => number }; +o2?.b(); + +declare const o3: { b: (() => { c: string }) | undefined }; +o3.b?.().c; + + +//// [callChain.2.js] +var _a, _b, _c, _d; +(_a = o1) === null || _a === void 0 ? void 0 : _a(); +(_b = o2) === null || _b === void 0 ? void 0 : _b.b(); +(_d = (_c = o3).b) === null || _d === void 0 ? void 0 : _d.call(_c).c; diff --git a/tests/baselines/reference/callChain.2.symbols b/tests/baselines/reference/callChain.2.symbols new file mode 100644 index 0000000000000..26c8859940e74 --- /dev/null +++ b/tests/baselines/reference/callChain.2.symbols @@ -0,0 +1,28 @@ +=== tests/cases/conformance/expressions/optionalChaining/callChain/callChain.2.ts === +declare const o1: undefined | (() => number); +>o1 : Symbol(o1, Decl(callChain.2.ts, 0, 13)) + +o1?.(); +>o1 : Symbol(o1, Decl(callChain.2.ts, 0, 13)) + +declare const o2: undefined | { b: () => number }; +>o2 : Symbol(o2, Decl(callChain.2.ts, 3, 13)) +>b : Symbol(b, Decl(callChain.2.ts, 3, 31)) + +o2?.b(); +>o2?.b : Symbol(b, Decl(callChain.2.ts, 3, 31)) +>o2 : Symbol(o2, Decl(callChain.2.ts, 3, 13)) +>b : Symbol(b, Decl(callChain.2.ts, 3, 31)) + +declare const o3: { b: (() => { c: string }) | undefined }; +>o3 : Symbol(o3, Decl(callChain.2.ts, 6, 13)) +>b : Symbol(b, Decl(callChain.2.ts, 6, 19)) +>c : Symbol(c, Decl(callChain.2.ts, 6, 31)) + +o3.b?.().c; +>o3.b?.().c : Symbol(c, Decl(callChain.2.ts, 6, 31)) +>o3.b : Symbol(b, Decl(callChain.2.ts, 6, 19)) +>o3 : Symbol(o3, Decl(callChain.2.ts, 6, 13)) +>b : Symbol(b, Decl(callChain.2.ts, 6, 19)) +>c : Symbol(c, Decl(callChain.2.ts, 6, 31)) + diff --git a/tests/baselines/reference/callChain.2.types b/tests/baselines/reference/callChain.2.types new file mode 100644 index 0000000000000..53ea569cf8e59 --- /dev/null +++ b/tests/baselines/reference/callChain.2.types @@ -0,0 +1,31 @@ +=== tests/cases/conformance/expressions/optionalChaining/callChain/callChain.2.ts === +declare const o1: undefined | (() => number); +>o1 : () => number + +o1?.(); +>o1?.() : number +>o1 : () => number + +declare const o2: undefined | { b: () => number }; +>o2 : { b: () => number; } +>b : () => number + +o2?.b(); +>o2?.b() : number +>o2?.b : () => number +>o2 : { b: () => number; } +>b : () => number + +declare const o3: { b: (() => { c: string }) | undefined }; +>o3 : { b: () => { c: string; }; } +>b : () => { c: string; } +>c : string + +o3.b?.().c; +>o3.b?.().c : string +>o3.b?.() : { c: string; } +>o3.b : () => { c: string; } +>o3 : { b: () => { c: string; }; } +>b : () => { c: string; } +>c : string + diff --git a/tests/baselines/reference/elementAccessChain.2.js b/tests/baselines/reference/elementAccessChain.2.js new file mode 100644 index 0000000000000..4212e5a20b110 --- /dev/null +++ b/tests/baselines/reference/elementAccessChain.2.js @@ -0,0 +1,20 @@ +//// [elementAccessChain.2.ts] +declare const o1: undefined | { b: string }; +o1?.["b"]; + +declare const o2: undefined | { b: { c: string } }; +o2?.["b"].c; +o2?.b["c"]; + +declare const o3: { b: undefined | { c: string } }; +o3["b"]?.c; +o3.b?.["c"]; + + +//// [elementAccessChain.2.js] +var _a, _b, _c, _d, _e; +(_a = o1) === null || _a === void 0 ? void 0 : _a["b"]; +(_b = o2) === null || _b === void 0 ? void 0 : _b["b"].c; +(_c = o2) === null || _c === void 0 ? void 0 : _c.b["c"]; +(_d = o3["b"]) === null || _d === void 0 ? void 0 : _d.c; +(_e = o3.b) === null || _e === void 0 ? void 0 : _e["c"]; diff --git a/tests/baselines/reference/elementAccessChain.2.symbols b/tests/baselines/reference/elementAccessChain.2.symbols new file mode 100644 index 0000000000000..689e8ec94cd87 --- /dev/null +++ b/tests/baselines/reference/elementAccessChain.2.symbols @@ -0,0 +1,43 @@ +=== tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.2.ts === +declare const o1: undefined | { b: string }; +>o1 : Symbol(o1, Decl(elementAccessChain.2.ts, 0, 13)) +>b : Symbol(b, Decl(elementAccessChain.2.ts, 0, 31)) + +o1?.["b"]; +>o1 : Symbol(o1, Decl(elementAccessChain.2.ts, 0, 13)) +>"b" : Symbol(b, Decl(elementAccessChain.2.ts, 0, 31)) + +declare const o2: undefined | { b: { c: string } }; +>o2 : Symbol(o2, Decl(elementAccessChain.2.ts, 3, 13)) +>b : Symbol(b, Decl(elementAccessChain.2.ts, 3, 31)) +>c : Symbol(c, Decl(elementAccessChain.2.ts, 3, 36)) + +o2?.["b"].c; +>o2?.["b"].c : Symbol(c, Decl(elementAccessChain.2.ts, 3, 36)) +>o2 : Symbol(o2, Decl(elementAccessChain.2.ts, 3, 13)) +>"b" : Symbol(b, Decl(elementAccessChain.2.ts, 3, 31)) +>c : Symbol(c, Decl(elementAccessChain.2.ts, 3, 36)) + +o2?.b["c"]; +>o2?.b : Symbol(b, Decl(elementAccessChain.2.ts, 3, 31)) +>o2 : Symbol(o2, Decl(elementAccessChain.2.ts, 3, 13)) +>b : Symbol(b, Decl(elementAccessChain.2.ts, 3, 31)) +>"c" : Symbol(c, Decl(elementAccessChain.2.ts, 3, 36)) + +declare const o3: { b: undefined | { c: string } }; +>o3 : Symbol(o3, Decl(elementAccessChain.2.ts, 7, 13)) +>b : Symbol(b, Decl(elementAccessChain.2.ts, 7, 19)) +>c : Symbol(c, Decl(elementAccessChain.2.ts, 7, 36)) + +o3["b"]?.c; +>o3["b"]?.c : Symbol(c, Decl(elementAccessChain.2.ts, 7, 36)) +>o3 : Symbol(o3, Decl(elementAccessChain.2.ts, 7, 13)) +>"b" : Symbol(b, Decl(elementAccessChain.2.ts, 7, 19)) +>c : Symbol(c, Decl(elementAccessChain.2.ts, 7, 36)) + +o3.b?.["c"]; +>o3.b : Symbol(b, Decl(elementAccessChain.2.ts, 7, 19)) +>o3 : Symbol(o3, Decl(elementAccessChain.2.ts, 7, 13)) +>b : Symbol(b, Decl(elementAccessChain.2.ts, 7, 19)) +>"c" : Symbol(c, Decl(elementAccessChain.2.ts, 7, 36)) + diff --git a/tests/baselines/reference/elementAccessChain.2.types b/tests/baselines/reference/elementAccessChain.2.types new file mode 100644 index 0000000000000..ef6b56075246c --- /dev/null +++ b/tests/baselines/reference/elementAccessChain.2.types @@ -0,0 +1,48 @@ +=== tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.2.ts === +declare const o1: undefined | { b: string }; +>o1 : { b: string; } +>b : string + +o1?.["b"]; +>o1?.["b"] : string +>o1 : { b: string; } +>"b" : "b" + +declare const o2: undefined | { b: { c: string } }; +>o2 : { b: { c: string; }; } +>b : { c: string; } +>c : string + +o2?.["b"].c; +>o2?.["b"].c : string +>o2?.["b"] : { c: string; } +>o2 : { b: { c: string; }; } +>"b" : "b" +>c : string + +o2?.b["c"]; +>o2?.b["c"] : string +>o2?.b : { c: string; } +>o2 : { b: { c: string; }; } +>b : { c: string; } +>"c" : "c" + +declare const o3: { b: undefined | { c: string } }; +>o3 : { b: { c: string; }; } +>b : { c: string; } +>c : string + +o3["b"]?.c; +>o3["b"]?.c : string +>o3["b"] : { c: string; } +>o3 : { b: { c: string; }; } +>"b" : "b" +>c : string + +o3.b?.["c"]; +>o3.b?.["c"] : string +>o3.b : { c: string; } +>o3 : { b: { c: string; }; } +>b : { c: string; } +>"c" : "c" + diff --git a/tests/baselines/reference/propertyAccessChain.2.js b/tests/baselines/reference/propertyAccessChain.2.js new file mode 100644 index 0000000000000..939e9830b549f --- /dev/null +++ b/tests/baselines/reference/propertyAccessChain.2.js @@ -0,0 +1,16 @@ +//// [propertyAccessChain.2.ts] +declare const o1: undefined | { b: string }; +o1?.b; + +declare const o2: undefined | { b: { c: string } }; +o2?.b.c; + +declare const o3: { b: undefined | { c: string } }; +o3.b?.c; + + +//// [propertyAccessChain.2.js] +var _a, _b, _c; +(_a = o1) === null || _a === void 0 ? void 0 : _a.b; +(_b = o2) === null || _b === void 0 ? void 0 : _b.b.c; +(_c = o3.b) === null || _c === void 0 ? void 0 : _c.c; diff --git a/tests/baselines/reference/propertyAccessChain.2.symbols b/tests/baselines/reference/propertyAccessChain.2.symbols new file mode 100644 index 0000000000000..2d4fd96896696 --- /dev/null +++ b/tests/baselines/reference/propertyAccessChain.2.symbols @@ -0,0 +1,34 @@ +=== tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.2.ts === +declare const o1: undefined | { b: string }; +>o1 : Symbol(o1, Decl(propertyAccessChain.2.ts, 0, 13)) +>b : Symbol(b, Decl(propertyAccessChain.2.ts, 0, 31)) + +o1?.b; +>o1?.b : Symbol(b, Decl(propertyAccessChain.2.ts, 0, 31)) +>o1 : Symbol(o1, Decl(propertyAccessChain.2.ts, 0, 13)) +>b : Symbol(b, Decl(propertyAccessChain.2.ts, 0, 31)) + +declare const o2: undefined | { b: { c: string } }; +>o2 : Symbol(o2, Decl(propertyAccessChain.2.ts, 3, 13)) +>b : Symbol(b, Decl(propertyAccessChain.2.ts, 3, 31)) +>c : Symbol(c, Decl(propertyAccessChain.2.ts, 3, 36)) + +o2?.b.c; +>o2?.b.c : Symbol(c, Decl(propertyAccessChain.2.ts, 3, 36)) +>o2?.b : Symbol(b, Decl(propertyAccessChain.2.ts, 3, 31)) +>o2 : Symbol(o2, Decl(propertyAccessChain.2.ts, 3, 13)) +>b : Symbol(b, Decl(propertyAccessChain.2.ts, 3, 31)) +>c : Symbol(c, Decl(propertyAccessChain.2.ts, 3, 36)) + +declare const o3: { b: undefined | { c: string } }; +>o3 : Symbol(o3, Decl(propertyAccessChain.2.ts, 6, 13)) +>b : Symbol(b, Decl(propertyAccessChain.2.ts, 6, 19)) +>c : Symbol(c, Decl(propertyAccessChain.2.ts, 6, 36)) + +o3.b?.c; +>o3.b?.c : Symbol(c, Decl(propertyAccessChain.2.ts, 6, 36)) +>o3.b : Symbol(b, Decl(propertyAccessChain.2.ts, 6, 19)) +>o3 : Symbol(o3, Decl(propertyAccessChain.2.ts, 6, 13)) +>b : Symbol(b, Decl(propertyAccessChain.2.ts, 6, 19)) +>c : Symbol(c, Decl(propertyAccessChain.2.ts, 6, 36)) + diff --git a/tests/baselines/reference/propertyAccessChain.2.types b/tests/baselines/reference/propertyAccessChain.2.types new file mode 100644 index 0000000000000..6a046048f15a9 --- /dev/null +++ b/tests/baselines/reference/propertyAccessChain.2.types @@ -0,0 +1,34 @@ +=== tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.2.ts === +declare const o1: undefined | { b: string }; +>o1 : { b: string; } +>b : string + +o1?.b; +>o1?.b : string +>o1 : { b: string; } +>b : string + +declare const o2: undefined | { b: { c: string } }; +>o2 : { b: { c: string; }; } +>b : { c: string; } +>c : string + +o2?.b.c; +>o2?.b.c : string +>o2?.b : { c: string; } +>o2 : { b: { c: string; }; } +>b : { c: string; } +>c : string + +declare const o3: { b: undefined | { c: string } }; +>o3 : { b: { c: string; }; } +>b : { c: string; } +>c : string + +o3.b?.c; +>o3.b?.c : string +>o3.b : { c: string; } +>o3 : { b: { c: string; }; } +>b : { c: string; } +>c : string + diff --git a/tests/baselines/reference/taggedTemplateChain.errors.txt b/tests/baselines/reference/taggedTemplateChain.errors.txt new file mode 100644 index 0000000000000..dc796115ac887 --- /dev/null +++ b/tests/baselines/reference/taggedTemplateChain.errors.txt @@ -0,0 +1,13 @@ +tests/cases/conformance/expressions/optionalChaining/taggedTemplateChain/taggedTemplateChain.ts(2,4): error TS1357: Tagged template expressions are not permitted in an optional chain. +tests/cases/conformance/expressions/optionalChaining/taggedTemplateChain/taggedTemplateChain.ts(4,4): error TS1357: Tagged template expressions are not permitted in an optional chain. + + +==== tests/cases/conformance/expressions/optionalChaining/taggedTemplateChain/taggedTemplateChain.ts (2 errors) ==== + declare let a: any; + a?.`b`; + ~~~ +!!! error TS1357: Tagged template expressions are not permitted in an optional chain. + + a?.`b${1}c`; + ~~~~~~~~ +!!! error TS1357: Tagged template expressions are not permitted in an optional chain. \ No newline at end of file diff --git a/tests/baselines/reference/taggedTemplateChain.js b/tests/baselines/reference/taggedTemplateChain.js new file mode 100644 index 0000000000000..c96237185fb45 --- /dev/null +++ b/tests/baselines/reference/taggedTemplateChain.js @@ -0,0 +1,13 @@ +//// [taggedTemplateChain.ts] +declare let a: any; +a?.`b`; + +a?.`b${1}c`; + +//// [taggedTemplateChain.js] +var __makeTemplateObject = (this && this.__makeTemplateObject) || function (cooked, raw) { + if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; } + return cooked; +}; +a(__makeTemplateObject(["b"], ["b"])); +a(__makeTemplateObject(["b", "c"], ["b", "c"]), 1); diff --git a/tests/baselines/reference/taggedTemplateChain.symbols b/tests/baselines/reference/taggedTemplateChain.symbols new file mode 100644 index 0000000000000..4dde630cab6ac --- /dev/null +++ b/tests/baselines/reference/taggedTemplateChain.symbols @@ -0,0 +1,10 @@ +=== tests/cases/conformance/expressions/optionalChaining/taggedTemplateChain/taggedTemplateChain.ts === +declare let a: any; +>a : Symbol(a, Decl(taggedTemplateChain.ts, 0, 11)) + +a?.`b`; +>a : Symbol(a, Decl(taggedTemplateChain.ts, 0, 11)) + +a?.`b${1}c`; +>a : Symbol(a, Decl(taggedTemplateChain.ts, 0, 11)) + diff --git a/tests/baselines/reference/taggedTemplateChain.types b/tests/baselines/reference/taggedTemplateChain.types new file mode 100644 index 0000000000000..9690fc22f6991 --- /dev/null +++ b/tests/baselines/reference/taggedTemplateChain.types @@ -0,0 +1,15 @@ +=== tests/cases/conformance/expressions/optionalChaining/taggedTemplateChain/taggedTemplateChain.ts === +declare let a: any; +>a : any + +a?.`b`; +>a?.`b` : any +>a : any +>`b` : "b" + +a?.`b${1}c`; +>a?.`b${1}c` : any +>a : any +>`b${1}c` : string +>1 : 1 + diff --git a/tests/cases/conformance/expressions/optionalChaining/callChain/callChain.2.ts b/tests/cases/conformance/expressions/optionalChaining/callChain/callChain.2.ts new file mode 100644 index 0000000000000..58e51858da6a1 --- /dev/null +++ b/tests/cases/conformance/expressions/optionalChaining/callChain/callChain.2.ts @@ -0,0 +1,10 @@ +// @strict: false + +declare const o1: undefined | (() => number); +o1?.(); + +declare const o2: undefined | { b: () => number }; +o2?.b(); + +declare const o3: { b: (() => { c: string }) | undefined }; +o3.b?.().c; diff --git a/tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.2.ts b/tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.2.ts new file mode 100644 index 0000000000000..00834b4d968a0 --- /dev/null +++ b/tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.2.ts @@ -0,0 +1,12 @@ +// @strict: false + +declare const o1: undefined | { b: string }; +o1?.["b"]; + +declare const o2: undefined | { b: { c: string } }; +o2?.["b"].c; +o2?.b["c"]; + +declare const o3: { b: undefined | { c: string } }; +o3["b"]?.c; +o3.b?.["c"]; diff --git a/tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.2.ts b/tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.2.ts new file mode 100644 index 0000000000000..93e37f0b48f64 --- /dev/null +++ b/tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.2.ts @@ -0,0 +1,10 @@ +// @strict: false + +declare const o1: undefined | { b: string }; +o1?.b; + +declare const o2: undefined | { b: { c: string } }; +o2?.b.c; + +declare const o3: { b: undefined | { c: string } }; +o3.b?.c; diff --git a/tests/cases/conformance/expressions/optionalChaining/taggedTemplateChain/taggedTemplateChain.ts b/tests/cases/conformance/expressions/optionalChaining/taggedTemplateChain/taggedTemplateChain.ts new file mode 100644 index 0000000000000..6a3bb04336e63 --- /dev/null +++ b/tests/cases/conformance/expressions/optionalChaining/taggedTemplateChain/taggedTemplateChain.ts @@ -0,0 +1,4 @@ +declare let a: any; +a?.`b`; + +a?.`b${1}c`; \ No newline at end of file From 0f5d5d69f15a5b4c902abf521693c9e5fb1477ab Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Mon, 23 Sep 2019 14:50:07 -0700 Subject: [PATCH 03/19] Prototype --- src/services/completions.ts | 87 ++++++++++++++++++++++++++----------- 1 file changed, 62 insertions(+), 25 deletions(-) diff --git a/src/services/completions.ts b/src/services/completions.ts index 9d5fef67f2900..68c6c40c7f1c4 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -11,28 +11,42 @@ namespace ts.Completions { } export type Log = (message: string) => void; - const enum SymbolOriginInfoKind { ThisType, SymbolMemberNoExport, SymbolMemberExport, Export, Promise } - type SymbolOriginInfo = { kind: SymbolOriginInfoKind.ThisType } | { kind: SymbolOriginInfoKind.Promise } | { kind: SymbolOriginInfoKind.SymbolMemberNoExport } | SymbolOriginInfoExport; - interface SymbolOriginInfoExport { - kind: SymbolOriginInfoKind.SymbolMemberExport | SymbolOriginInfoKind.Export; + const enum SymbolOriginInfoKind { + ThisType = 1 << 0, + SymbolMemberNoExport = 1 << 1, + SymbolMemberExport = 1 << 2, + Nullable = 1 << 3, + Export = 1 << 4, + Promise = 1 << 4 + } + + interface SymbolOriginInfo { + kind: SymbolOriginInfoKind; + } + + interface SymbolOriginInfoExport extends SymbolOriginInfo { + kind: SymbolOriginInfoKind; moduleSymbol: Symbol; isDefaultExport: boolean; } function originIsSymbolMember(origin: SymbolOriginInfo): boolean { - return origin.kind === SymbolOriginInfoKind.SymbolMemberExport || origin.kind === SymbolOriginInfoKind.SymbolMemberNoExport; + return !!(origin.kind & SymbolOriginInfoKind.SymbolMemberExport || origin.kind & SymbolOriginInfoKind.SymbolMemberNoExport); } function originIsExport(origin: SymbolOriginInfo): origin is SymbolOriginInfoExport { - return origin.kind === SymbolOriginInfoKind.SymbolMemberExport || origin.kind === SymbolOriginInfoKind.Export; + return !!(origin.kind & SymbolOriginInfoKind.SymbolMemberExport || origin.kind & SymbolOriginInfoKind.Export); } function originIsPromise(origin: SymbolOriginInfo): boolean { - return origin.kind === SymbolOriginInfoKind.Promise; + return !!(origin.kind & SymbolOriginInfoKind.Promise); + } + function originIsNullableMember(origin: SymbolOriginInfo): boolean { + return !!(origin.kind & SymbolOriginInfoKind.Nullable); } /** * Map from symbol id -> SymbolOriginInfo. * Only populated for symbols that come from other modules. */ - type SymbolOriginInfoMap = (SymbolOriginInfo | undefined)[]; + type SymbolOriginInfoMap = (SymbolOriginInfo | SymbolOriginInfoExport | undefined)[]; type SymbolSortTextMap = (SortText | undefined)[]; @@ -249,14 +263,23 @@ namespace ts.Completions { ): CompletionEntry | undefined { let insertText: string | undefined; let replacementSpan: TextSpan | undefined; - if (origin && origin.kind === SymbolOriginInfoKind.ThisType) { - insertText = needsConvertPropertyAccess ? `this[${quote(name, preferences)}]` : `this.${name}`; + const insertQuestionDot = origin && originIsNullableMember(origin); + const useBraces = origin && originIsSymbolMember(origin) || needsConvertPropertyAccess; + if (origin && origin.kind & SymbolOriginInfoKind.ThisType) { + insertText = needsConvertPropertyAccess + ? `this${insertQuestionDot ? "?." : ""}[${quote(name, preferences)}]` + : `this${insertQuestionDot ? "?." : "."}${name}`; } // We should only have needsConvertPropertyAccess if there's a property access to convert. But see #21790. // Somehow there was a global with a non-identifier name. Hopefully someone will complain about getting a "foo bar" global completion and provide a repro. - else if ((origin && originIsSymbolMember(origin) || needsConvertPropertyAccess) && propertyAccessToConvert) { - insertText = needsConvertPropertyAccess ? `[${quote(name, preferences)}]` : `[${name}]`; - const dot = findChildOfKind(propertyAccessToConvert, SyntaxKind.DotToken, sourceFile)!; + else if ((useBraces || insertQuestionDot) && propertyAccessToConvert) { + insertText = useBraces ? needsConvertPropertyAccess ? `[${quote(name, preferences)}]` : `[${name}]` : name; + if (insertQuestionDot) { + insertText = `?.${insertText}`; + } + + const dot = findChildOfKind(propertyAccessToConvert, SyntaxKind.DotToken, sourceFile) || + findChildOfKind(propertyAccessToConvert, SyntaxKind.QuestionDotToken, sourceFile)!; // If the text after the '.' starts with this name, write over it. Else, add new text. const end = startsWith(name, propertyAccessToConvert.name.text) ? propertyAccessToConvert.name.end : dot.end; replacementSpan = createTextSpanFromBounds(dot.getStart(sourceFile), end); @@ -272,7 +295,7 @@ namespace ts.Completions { if (origin && originIsPromise(origin) && propertyAccessToConvert) { if (insertText === undefined) insertText = name; const awaitText = `(await ${propertyAccessToConvert.expression.getText()})`; - insertText = needsConvertPropertyAccess ? `${awaitText}${insertText}` : `${awaitText}.${insertText}`; + insertText = needsConvertPropertyAccess ? `${awaitText}${insertText}` : `${awaitText}${insertQuestionDot ? "?." : "."}${insertText}`; replacementSpan = createTextSpanFromBounds(propertyAccessToConvert.getStart(sourceFile), propertyAccessToConvert.end); } @@ -1003,7 +1026,10 @@ namespace ts.Completions { if (!isTypeLocation && symbol.declarations && symbol.declarations.some(d => d.kind !== SyntaxKind.SourceFile && d.kind !== SyntaxKind.ModuleDeclaration && d.kind !== SyntaxKind.EnumDeclaration)) { - addTypeProperties(removeOptionality(typeChecker.getTypeOfSymbolAtLocation(symbol, node), isRightOfQuestionDot, isOptional), !!(node.flags & NodeFlags.AwaitContext)); + const type = removeOptionality(typeChecker.getTypeOfSymbolAtLocation(symbol, node), isRightOfQuestionDot, isOptional); + const nonNullType = type.getNonNullableType(); + const insertQuestionDot = isRightOfDot && !isRightOfQuestionDot && type !== nonNullType; + addTypeProperties(nonNullType, !!(node.flags & NodeFlags.AwaitContext), insertQuestionDot); } return; @@ -1018,11 +1044,14 @@ namespace ts.Completions { } if (!isTypeLocation) { - addTypeProperties(removeOptionality(typeChecker.getTypeAtLocation(node), isRightOfQuestionDot, isOptional), !!(node.flags & NodeFlags.AwaitContext)); + const type = removeOptionality(typeChecker.getTypeAtLocation(node), isRightOfQuestionDot, isOptional); + const nonNullType = type.getNonNullableType(); + const insertQuestionDot = isRightOfDot && !isRightOfQuestionDot && type !== nonNullType; + addTypeProperties(nonNullType, !!(node.flags & NodeFlags.AwaitContext), insertQuestionDot); } } - function addTypeProperties(type: Type, insertAwait?: boolean): void { + function addTypeProperties(type: Type, insertAwait: boolean, insertQuestionDot: boolean): void { isNewIdentifierLocation = !!type.getStringIndexType(); const propertyAccess = node.kind === SyntaxKind.ImportType ? node : node.parent; @@ -1037,7 +1066,7 @@ namespace ts.Completions { else { for (const symbol of type.getApparentProperties()) { if (typeChecker.isValidPropertyAccessForCompletions(propertyAccess, type, symbol)) { - addPropertySymbol(symbol); + addPropertySymbol(symbol, /*insertAwait*/ false, insertQuestionDot); } } } @@ -1047,14 +1076,14 @@ namespace ts.Completions { if (promiseType) { for (const symbol of promiseType.getApparentProperties()) { if (typeChecker.isValidPropertyAccessForCompletions(propertyAccess, promiseType, symbol)) { - addPropertySymbol(symbol, /* insertAwait */ true); + addPropertySymbol(symbol, /* insertAwait */ true, insertQuestionDot); } } } } } - function addPropertySymbol(symbol: Symbol, insertAwait?: boolean) { + function addPropertySymbol(symbol: Symbol, insertAwait: boolean, insertQuestionDot: boolean) { // For a computed property with an accessible name like `Symbol.iterator`, // we'll add a completion for the *name* `Symbol` instead of for the property. // If this is e.g. [Symbol.iterator], add a completion for `Symbol`. @@ -1068,22 +1097,30 @@ namespace ts.Completions { symbols.push(firstAccessibleSymbol); const moduleSymbol = firstAccessibleSymbol.parent; symbolToOriginInfoMap[getSymbolId(firstAccessibleSymbol)] = - !moduleSymbol || !isExternalModuleSymbol(moduleSymbol) ? { kind: SymbolOriginInfoKind.SymbolMemberNoExport } : { kind: SymbolOriginInfoKind.SymbolMemberExport, moduleSymbol, isDefaultExport: false }; + !moduleSymbol || !isExternalModuleSymbol(moduleSymbol) + ? { kind: getNullableSymbolOriginInfoKind(SymbolOriginInfoKind.SymbolMemberNoExport) } + : { kind: getNullableSymbolOriginInfoKind(SymbolOriginInfoKind.SymbolMemberExport), moduleSymbol, isDefaultExport: false }; } else if (preferences.includeCompletionsWithInsertText) { - addPromiseSymbolOriginInfo(symbol); + addSymbolOriginInfo(symbol); symbols.push(symbol); } } else { - addPromiseSymbolOriginInfo(symbol); + addSymbolOriginInfo(symbol); symbols.push(symbol); } - function addPromiseSymbolOriginInfo (symbol: Symbol) { + function addSymbolOriginInfo(symbol: Symbol) { if (insertAwait && preferences.includeCompletionsWithInsertText && !symbolToOriginInfoMap[getSymbolId(symbol)]) { - symbolToOriginInfoMap[getSymbolId(symbol)] = { kind: SymbolOriginInfoKind.Promise }; + symbolToOriginInfoMap[getSymbolId(symbol)] = { kind: getNullableSymbolOriginInfoKind(SymbolOriginInfoKind.Promise) }; } + else if (insertQuestionDot) { + symbolToOriginInfoMap[getSymbolId(symbol)] = { kind: SymbolOriginInfoKind.Nullable }; + } + } + function getNullableSymbolOriginInfoKind(kind: SymbolOriginInfoKind) { + return insertQuestionDot ? kind | SymbolOriginInfoKind.Nullable : kind; } } From 7be47abcf5928b932523e7e722ae4ed3ed93a50e Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Mon, 23 Sep 2019 19:47:37 -0700 Subject: [PATCH 04/19] PR feedback --- src/compiler/checker.ts | 9 +- src/compiler/types.ts | 5 +- src/services/completions.ts | 6 +- .../reference/callChain.3.errors.txt | 23 ++ tests/baselines/reference/callChain.3.js | 22 ++ tests/baselines/reference/callChain.3.symbols | 60 ++++ tests/baselines/reference/callChain.3.types | 76 ++++++ tests/baselines/reference/callChain.js | 67 ++++- tests/baselines/reference/callChain.symbols | 154 +++++++++-- tests/baselines/reference/callChain.types | 256 +++++++++++++++++- .../baselines/reference/elementAccessChain.js | 18 +- .../reference/elementAccessChain.symbols | 60 ++++ .../reference/elementAccessChain.types | 84 ++++++ .../reference/propertyAccessChain.js | 10 +- .../reference/propertyAccessChain.symbols | 36 +++ .../reference/propertyAccessChain.types | 37 +++ .../optionalChaining/callChain/callChain.3.ts | 12 + .../optionalChaining/callChain/callChain.ts | 31 ++- .../elementAccessChain/elementAccessChain.ts | 10 + .../propertyAccessChain.ts | 6 + 20 files changed, 928 insertions(+), 54 deletions(-) create mode 100644 tests/baselines/reference/callChain.3.errors.txt create mode 100644 tests/baselines/reference/callChain.3.js create mode 100644 tests/baselines/reference/callChain.3.symbols create mode 100644 tests/baselines/reference/callChain.3.types create mode 100644 tests/cases/conformance/expressions/optionalChaining/callChain/callChain.3.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2b2b6849c3c78..9424976673149 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15025,15 +15025,12 @@ namespace ts { 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. - if (isNullableType(type)) { - isOptional = true; - type = getNonNullableType(type); - } - return { isOptional, type }; + isOptional = isNullableType(type); + return { isOptional, type: isOptional ? getNonNullableType(type) : type }; } // If we do not have a questionDotToken, then we are an OptionalChain and we remove the optionalType and - // add it back into the result, if needed. + // indicate whether we need to add optionalType back into the result. const nonOptionalType = removeOptionalTypeMarker(type); if (nonOptionalType !== type) { isOptional = true; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index db56ae28ac216..16b544c5b5d9f 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -530,7 +530,7 @@ namespace ts { NestedNamespace = 1 << 2, // Namespace declaration Synthesized = 1 << 3, // Node was synthesized during transformation Namespace = 1 << 4, // Namespace declaration - OptionalChain = 1 << 5, // Chained MemberExpression rooted to a pseudo-OptionalExpression + OptionalChain = 1 << 5, // Chained MemberExpression rooted to a pseudo-OptionalExpression ExportContext = 1 << 6, // Export context (initialized by binding) ContainsThis = 1 << 7, // Interface contains references to "this" HasImplicitReturn = 1 << 8, // If function implicitly returns on one of codepaths (initialized by binding) @@ -1788,7 +1788,6 @@ namespace ts { export interface PropertyAccessChain extends PropertyAccessExpression { _optionalChainBrand: any; - kind: SyntaxKind.PropertyAccessExpression; } export interface SuperPropertyAccessExpression extends PropertyAccessExpression { @@ -1810,7 +1809,6 @@ namespace ts { export interface ElementAccessChain extends ElementAccessExpression { _optionalChainBrand: any; - kind: SyntaxKind.ElementAccessExpression; } export interface SuperElementAccessExpression extends ElementAccessExpression { @@ -1830,7 +1828,6 @@ namespace ts { export interface CallChain extends CallExpression { _optionalChainBrand: any; - kind: SyntaxKind.CallExpression; } export type OptionalChain = diff --git a/src/services/completions.ts b/src/services/completions.ts index 4fc93a5abed57..eeba6842bf35a 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -256,7 +256,11 @@ namespace ts.Completions { // Somehow there was a global with a non-identifier name. Hopefully someone will complain about getting a "foo bar" global completion and provide a repro. else if ((origin && originIsSymbolMember(origin) || needsConvertPropertyAccess) && propertyAccessToConvert) { insertText = needsConvertPropertyAccess ? `[${quote(name, preferences)}]` : `[${name}]`; - const dot = findChildOfKind(propertyAccessToConvert, SyntaxKind.DotToken, sourceFile)!; + const dot = findChildOfKind(propertyAccessToConvert, SyntaxKind.DotToken, sourceFile) || + findChildOfKind(propertyAccessToConvert, SyntaxKind.QuestionDotToken, sourceFile); + if (!dot) { + return undefined; + } // If the text after the '.' starts with this name, write over it. Else, add new text. const end = startsWith(name, propertyAccessToConvert.name.text) ? propertyAccessToConvert.name.end : dot.end; replacementSpan = createTextSpanFromBounds(dot.getStart(sourceFile), end); diff --git a/tests/baselines/reference/callChain.3.errors.txt b/tests/baselines/reference/callChain.3.errors.txt new file mode 100644 index 0000000000000..6deb1db1b4bef --- /dev/null +++ b/tests/baselines/reference/callChain.3.errors.txt @@ -0,0 +1,23 @@ +tests/cases/conformance/expressions/optionalChaining/callChain/callChain.3.ts(3,7): error TS2322: Type 'number | undefined' is not assignable to type 'number'. + Type 'undefined' is not assignable to type 'number'. +tests/cases/conformance/expressions/optionalChaining/callChain/callChain.3.ts(4,7): error TS2322: Type 'number | undefined' is not assignable to type 'number'. + Type 'undefined' is not assignable to type 'number'. + + +==== tests/cases/conformance/expressions/optionalChaining/callChain/callChain.3.ts (2 errors) ==== + declare function absorb(): T; + declare const a: { m?(obj: {x: T}): T } | undefined; + const n1: number = a?.m?.({x: 12 }); // should be an error (`undefined` is not assignable to `number`) + ~~ +!!! error TS2322: Type 'number | undefined' is not assignable to type 'number'. +!!! error TS2322: Type 'undefined' is not assignable to type 'number'. + const n2: number = a?.m?.({x: absorb()}); // likewise + ~~ +!!! error TS2322: Type 'number | undefined' is not assignable to type 'number'. +!!! error TS2322: Type 'undefined' is not assignable to type 'number'. + const n3: number | undefined = a?.m?.({x: 12}); // should be ok + const n4: number | undefined = a?.m?.({x: absorb()}); // likewise + + // Also a test showing `!` vs `?` for good measure + let t1 = a?.m?.({x: 12}); + t1 = a!.m!({x: 12}); \ No newline at end of file diff --git a/tests/baselines/reference/callChain.3.js b/tests/baselines/reference/callChain.3.js new file mode 100644 index 0000000000000..2a6685d0d53f5 --- /dev/null +++ b/tests/baselines/reference/callChain.3.js @@ -0,0 +1,22 @@ +//// [callChain.3.ts] +declare function absorb(): T; +declare const a: { m?(obj: {x: T}): T } | undefined; +const n1: number = a?.m?.({x: 12 }); // should be an error (`undefined` is not assignable to `number`) +const n2: number = a?.m?.({x: absorb()}); // likewise +const n3: number | undefined = a?.m?.({x: 12}); // should be ok +const n4: number | undefined = a?.m?.({x: absorb()}); // likewise + +// Also a test showing `!` vs `?` for good measure +let t1 = a?.m?.({x: 12}); +t1 = a!.m!({x: 12}); + +//// [callChain.3.js] +"use strict"; +var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q; +var n1 = (_c = (_a = a) === null || _a === void 0 ? void 0 : (_b = _a).m) === null || _c === void 0 ? void 0 : _c.call(_b, { x: 12 }); // should be an error (`undefined` is not assignable to `number`) +var n2 = (_f = (_d = a) === null || _d === void 0 ? void 0 : (_e = _d).m) === null || _f === void 0 ? void 0 : _f.call(_e, { x: absorb() }); // likewise +var n3 = (_j = (_g = a) === null || _g === void 0 ? void 0 : (_h = _g).m) === null || _j === void 0 ? void 0 : _j.call(_h, { x: 12 }); // should be ok +var n4 = (_m = (_k = a) === null || _k === void 0 ? void 0 : (_l = _k).m) === null || _m === void 0 ? void 0 : _m.call(_l, { x: absorb() }); // likewise +// Also a test showing `!` vs `?` for good measure +var t1 = (_q = (_o = a) === null || _o === void 0 ? void 0 : (_p = _o).m) === null || _q === void 0 ? void 0 : _q.call(_p, { x: 12 }); +t1 = a.m({ x: 12 }); diff --git a/tests/baselines/reference/callChain.3.symbols b/tests/baselines/reference/callChain.3.symbols new file mode 100644 index 0000000000000..eb5a6ae9df2f0 --- /dev/null +++ b/tests/baselines/reference/callChain.3.symbols @@ -0,0 +1,60 @@ +=== tests/cases/conformance/expressions/optionalChaining/callChain/callChain.3.ts === +declare function absorb(): T; +>absorb : Symbol(absorb, Decl(callChain.3.ts, 0, 0)) +>T : Symbol(T, Decl(callChain.3.ts, 0, 24)) +>T : Symbol(T, Decl(callChain.3.ts, 0, 24)) + +declare const a: { m?(obj: {x: T}): T } | undefined; +>a : Symbol(a, Decl(callChain.3.ts, 1, 13)) +>m : Symbol(m, Decl(callChain.3.ts, 1, 18)) +>T : Symbol(T, Decl(callChain.3.ts, 1, 22)) +>obj : Symbol(obj, Decl(callChain.3.ts, 1, 25)) +>x : Symbol(x, Decl(callChain.3.ts, 1, 31)) +>T : Symbol(T, Decl(callChain.3.ts, 1, 22)) +>T : Symbol(T, Decl(callChain.3.ts, 1, 22)) + +const n1: number = a?.m?.({x: 12 }); // should be an error (`undefined` is not assignable to `number`) +>n1 : Symbol(n1, Decl(callChain.3.ts, 2, 5)) +>a?.m : Symbol(m, Decl(callChain.3.ts, 1, 18)) +>a : Symbol(a, Decl(callChain.3.ts, 1, 13)) +>m : Symbol(m, Decl(callChain.3.ts, 1, 18)) +>x : Symbol(x, Decl(callChain.3.ts, 2, 27)) + +const n2: number = a?.m?.({x: absorb()}); // likewise +>n2 : Symbol(n2, Decl(callChain.3.ts, 3, 5)) +>a?.m : Symbol(m, Decl(callChain.3.ts, 1, 18)) +>a : Symbol(a, Decl(callChain.3.ts, 1, 13)) +>m : Symbol(m, Decl(callChain.3.ts, 1, 18)) +>x : Symbol(x, Decl(callChain.3.ts, 3, 27)) +>absorb : Symbol(absorb, Decl(callChain.3.ts, 0, 0)) + +const n3: number | undefined = a?.m?.({x: 12}); // should be ok +>n3 : Symbol(n3, Decl(callChain.3.ts, 4, 5)) +>a?.m : Symbol(m, Decl(callChain.3.ts, 1, 18)) +>a : Symbol(a, Decl(callChain.3.ts, 1, 13)) +>m : Symbol(m, Decl(callChain.3.ts, 1, 18)) +>x : Symbol(x, Decl(callChain.3.ts, 4, 39)) + +const n4: number | undefined = a?.m?.({x: absorb()}); // likewise +>n4 : Symbol(n4, Decl(callChain.3.ts, 5, 5)) +>a?.m : Symbol(m, Decl(callChain.3.ts, 1, 18)) +>a : Symbol(a, Decl(callChain.3.ts, 1, 13)) +>m : Symbol(m, Decl(callChain.3.ts, 1, 18)) +>x : Symbol(x, Decl(callChain.3.ts, 5, 39)) +>absorb : Symbol(absorb, Decl(callChain.3.ts, 0, 0)) + +// Also a test showing `!` vs `?` for good measure +let t1 = a?.m?.({x: 12}); +>t1 : Symbol(t1, Decl(callChain.3.ts, 8, 3)) +>a?.m : Symbol(m, Decl(callChain.3.ts, 1, 18)) +>a : Symbol(a, Decl(callChain.3.ts, 1, 13)) +>m : Symbol(m, Decl(callChain.3.ts, 1, 18)) +>x : Symbol(x, Decl(callChain.3.ts, 8, 17)) + +t1 = a!.m!({x: 12}); +>t1 : Symbol(t1, Decl(callChain.3.ts, 8, 3)) +>a!.m : Symbol(m, Decl(callChain.3.ts, 1, 18)) +>a : Symbol(a, Decl(callChain.3.ts, 1, 13)) +>m : Symbol(m, Decl(callChain.3.ts, 1, 18)) +>x : Symbol(x, Decl(callChain.3.ts, 9, 12)) + diff --git a/tests/baselines/reference/callChain.3.types b/tests/baselines/reference/callChain.3.types new file mode 100644 index 0000000000000..7646b5418a7ee --- /dev/null +++ b/tests/baselines/reference/callChain.3.types @@ -0,0 +1,76 @@ +=== tests/cases/conformance/expressions/optionalChaining/callChain/callChain.3.ts === +declare function absorb(): T; +>absorb : () => T + +declare const a: { m?(obj: {x: T}): T } | undefined; +>a : { m?(obj: { x: T; }): T; } | undefined +>m : ((obj: { x: T; }) => T) | undefined +>obj : { x: T; } +>x : T + +const n1: number = a?.m?.({x: 12 }); // should be an error (`undefined` is not assignable to `number`) +>n1 : number +>a?.m?.({x: 12 }) : number | undefined +>a?.m : ((obj: { x: T; }) => T) | undefined +>a : { m?(obj: { x: T; }): T; } | undefined +>m : ((obj: { x: T; }) => T) | undefined +>{x: 12 } : { x: number; } +>x : number +>12 : 12 + +const n2: number = a?.m?.({x: absorb()}); // likewise +>n2 : number +>a?.m?.({x: absorb()}) : number | undefined +>a?.m : ((obj: { x: T; }) => T) | undefined +>a : { m?(obj: { x: T; }): T; } | undefined +>m : ((obj: { x: T; }) => T) | undefined +>{x: absorb()} : { x: number; } +>x : number +>absorb() : number +>absorb : () => T + +const n3: number | undefined = a?.m?.({x: 12}); // should be ok +>n3 : number | undefined +>a?.m?.({x: 12}) : number | undefined +>a?.m : ((obj: { x: T; }) => T) | undefined +>a : { m?(obj: { x: T; }): T; } | undefined +>m : ((obj: { x: T; }) => T) | undefined +>{x: 12} : { x: number; } +>x : number +>12 : 12 + +const n4: number | undefined = a?.m?.({x: absorb()}); // likewise +>n4 : number | undefined +>a?.m?.({x: absorb()}) : number | undefined +>a?.m : ((obj: { x: T; }) => T) | undefined +>a : { m?(obj: { x: T; }): T; } | undefined +>m : ((obj: { x: T; }) => T) | undefined +>{x: absorb()} : { x: number | undefined; } +>x : number | undefined +>absorb() : number | undefined +>absorb : () => T + +// Also a test showing `!` vs `?` for good measure +let t1 = a?.m?.({x: 12}); +>t1 : number | undefined +>a?.m?.({x: 12}) : number | undefined +>a?.m : ((obj: { x: T; }) => T) | undefined +>a : { m?(obj: { x: T; }): T; } | undefined +>m : ((obj: { x: T; }) => T) | undefined +>{x: 12} : { x: number; } +>x : number +>12 : 12 + +t1 = a!.m!({x: 12}); +>t1 = a!.m!({x: 12}) : number +>t1 : number | undefined +>a!.m!({x: 12}) : number +>a!.m! : (obj: { x: T; }) => T +>a!.m : ((obj: { x: T; }) => T) | undefined +>a! : { m?(obj: { x: T; }): T; } +>a : { m?(obj: { x: T; }): T; } | undefined +>m : ((obj: { x: T; }) => T) | undefined +>{x: 12} : { x: number; } +>x : number +>12 : 12 + diff --git a/tests/baselines/reference/callChain.js b/tests/baselines/reference/callChain.js index dde51814f9b8b..1ef797b5ad1b3 100644 --- a/tests/baselines/reference/callChain.js +++ b/tests/baselines/reference/callChain.js @@ -1,17 +1,70 @@ //// [callChain.ts] -declare const o1: undefined | (() => number); +declare const o1: undefined | ((...args: any[]) => number); o1?.(); +o1?.(1); +o1?.(...[1, 2]); +o1?.(1, ...[2, 3], 4); -declare const o2: undefined | { b: () => number }; +declare const o2: undefined | { b: (...args: any[]) => number }; o2?.b(); +o2?.b(1); +o2?.b(...[1, 2]); +o2?.b(1, ...[2, 3], 4); +o2?.["b"](); +o2?.["b"](1); +o2?.["b"](...[1, 2]); +o2?.["b"](1, ...[2, 3], 4); -declare const o3: { b: (() => { c: string }) | undefined }; +declare const o3: { b: ((...args: any[]) => { c: string }) | undefined }; o3.b?.().c; - +o3.b?.(1).c; +o3.b?.(...[1, 2]).c; +o3.b?.(1, ...[2, 3], 4).c; +o3.b?.()["c"]; +o3.b?.(1)["c"]; +o3.b?.(...[1, 2])["c"]; +o3.b?.(1, ...[2, 3], 4)["c"]; +o3["b"]?.().c; +o3["b"]?.(1).c; +o3["b"]?.(...[1, 2]).c; +o3["b"]?.(1, ...[2, 3], 4).c; + +declare const o4: undefined | ((f: (a: T) => T) => T); +declare function incr(x: number): number; +const v: number | undefined = o4?.(incr); //// [callChain.js] "use strict"; -var _a, _b, _c, _d; +var __spreadArrays = (this && this.__spreadArrays) || function () { + for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length; + for (var r = Array(s), k = 0, i = 0; i < il; i++) + for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) + r[k] = a[j]; + return r; +}; +var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12; (_a = o1) === null || _a === void 0 ? void 0 : _a(); -(_b = o2) === null || _b === void 0 ? void 0 : _b.b(); -(_d = (_c = o3).b) === null || _d === void 0 ? void 0 : _d.call(_c).c; +(_b = o1) === null || _b === void 0 ? void 0 : _b(1); +(_c = o1) === null || _c === void 0 ? void 0 : _c.apply(void 0, [1, 2]); +(_d = o1) === null || _d === void 0 ? void 0 : _d.apply(void 0, __spreadArrays([1], [2, 3], [4])); +(_e = o2) === null || _e === void 0 ? void 0 : _e.b(); +(_f = o2) === null || _f === void 0 ? void 0 : _f.b(1); +(_g = o2) === null || _g === void 0 ? void 0 : _g.b.apply(_g, [1, 2]); +(_h = o2) === null || _h === void 0 ? void 0 : _h.b.apply(_h, __spreadArrays([1], [2, 3], [4])); +(_j = o2) === null || _j === void 0 ? void 0 : _j["b"](); +(_k = o2) === null || _k === void 0 ? void 0 : _k["b"](1); +(_l = o2) === null || _l === void 0 ? void 0 : _l["b"].apply(_l, [1, 2]); +(_m = o2) === null || _m === void 0 ? void 0 : _m["b"].apply(_m, __spreadArrays([1], [2, 3], [4])); +(_p = (_o = o3).b) === null || _p === void 0 ? void 0 : _p.call(_o).c; +(_r = (_q = o3).b) === null || _r === void 0 ? void 0 : _r.call(_q, 1).c; +(_t = (_s = o3).b) === null || _t === void 0 ? void 0 : _t.call.apply(_t, __spreadArrays([_s], [1, 2])).c; +(_v = (_u = o3).b) === null || _v === void 0 ? void 0 : _v.call.apply(_v, __spreadArrays([_u, 1], [2, 3], [4])).c; +(_x = (_w = o3).b) === null || _x === void 0 ? void 0 : _x.call(_w)["c"]; +(_z = (_y = o3).b) === null || _z === void 0 ? void 0 : _z.call(_y, 1)["c"]; +(_1 = (_0 = o3).b) === null || _1 === void 0 ? void 0 : _1.call.apply(_1, __spreadArrays([_0], [1, 2]))["c"]; +(_3 = (_2 = o3).b) === null || _3 === void 0 ? void 0 : _3.call.apply(_3, __spreadArrays([_2, 1], [2, 3], [4]))["c"]; +(_5 = (_4 = o3)["b"]) === null || _5 === void 0 ? void 0 : _5.call(_4).c; +(_7 = (_6 = o3)["b"]) === null || _7 === void 0 ? void 0 : _7.call(_6, 1).c; +(_9 = (_8 = o3)["b"]) === null || _9 === void 0 ? void 0 : _9.call.apply(_9, __spreadArrays([_8], [1, 2])).c; +(_11 = (_10 = o3)["b"]) === null || _11 === void 0 ? void 0 : _11.call.apply(_11, __spreadArrays([_10, 1], [2, 3], [4])).c; +var v = (_12 = o4) === null || _12 === void 0 ? void 0 : _12(incr); diff --git a/tests/baselines/reference/callChain.symbols b/tests/baselines/reference/callChain.symbols index b8d537402e7ac..dd52388bda60f 100644 --- a/tests/baselines/reference/callChain.symbols +++ b/tests/baselines/reference/callChain.symbols @@ -1,28 +1,150 @@ === tests/cases/conformance/expressions/optionalChaining/callChain/callChain.ts === -declare const o1: undefined | (() => number); +declare const o1: undefined | ((...args: any[]) => number); >o1 : Symbol(o1, Decl(callChain.ts, 0, 13)) +>args : Symbol(args, Decl(callChain.ts, 0, 32)) o1?.(); >o1 : Symbol(o1, Decl(callChain.ts, 0, 13)) -declare const o2: undefined | { b: () => number }; ->o2 : Symbol(o2, Decl(callChain.ts, 3, 13)) ->b : Symbol(b, Decl(callChain.ts, 3, 31)) +o1?.(1); +>o1 : Symbol(o1, Decl(callChain.ts, 0, 13)) + +o1?.(...[1, 2]); +>o1 : Symbol(o1, Decl(callChain.ts, 0, 13)) + +o1?.(1, ...[2, 3], 4); +>o1 : Symbol(o1, Decl(callChain.ts, 0, 13)) + +declare const o2: undefined | { b: (...args: any[]) => number }; +>o2 : Symbol(o2, Decl(callChain.ts, 6, 13)) +>b : Symbol(b, Decl(callChain.ts, 6, 31)) +>args : Symbol(args, Decl(callChain.ts, 6, 36)) o2?.b(); ->o2?.b : Symbol(b, Decl(callChain.ts, 3, 31)) ->o2 : Symbol(o2, Decl(callChain.ts, 3, 13)) ->b : Symbol(b, Decl(callChain.ts, 3, 31)) +>o2?.b : Symbol(b, Decl(callChain.ts, 6, 31)) +>o2 : Symbol(o2, Decl(callChain.ts, 6, 13)) +>b : Symbol(b, Decl(callChain.ts, 6, 31)) + +o2?.b(1); +>o2?.b : Symbol(b, Decl(callChain.ts, 6, 31)) +>o2 : Symbol(o2, Decl(callChain.ts, 6, 13)) +>b : Symbol(b, Decl(callChain.ts, 6, 31)) -declare const o3: { b: (() => { c: string }) | undefined }; ->o3 : Symbol(o3, Decl(callChain.ts, 6, 13)) ->b : Symbol(b, Decl(callChain.ts, 6, 19)) ->c : Symbol(c, Decl(callChain.ts, 6, 31)) +o2?.b(...[1, 2]); +>o2?.b : Symbol(b, Decl(callChain.ts, 6, 31)) +>o2 : Symbol(o2, Decl(callChain.ts, 6, 13)) +>b : Symbol(b, Decl(callChain.ts, 6, 31)) + +o2?.b(1, ...[2, 3], 4); +>o2?.b : Symbol(b, Decl(callChain.ts, 6, 31)) +>o2 : Symbol(o2, Decl(callChain.ts, 6, 13)) +>b : Symbol(b, Decl(callChain.ts, 6, 31)) + +o2?.["b"](); +>o2 : Symbol(o2, Decl(callChain.ts, 6, 13)) + +o2?.["b"](1); +>o2 : Symbol(o2, Decl(callChain.ts, 6, 13)) + +o2?.["b"](...[1, 2]); +>o2 : Symbol(o2, Decl(callChain.ts, 6, 13)) + +o2?.["b"](1, ...[2, 3], 4); +>o2 : Symbol(o2, Decl(callChain.ts, 6, 13)) + +declare const o3: { b: ((...args: any[]) => { c: string }) | undefined }; +>o3 : Symbol(o3, Decl(callChain.ts, 16, 13)) +>b : Symbol(b, Decl(callChain.ts, 16, 19)) +>args : Symbol(args, Decl(callChain.ts, 16, 25)) +>c : Symbol(c, Decl(callChain.ts, 16, 45)) o3.b?.().c; ->o3.b?.().c : Symbol(c, Decl(callChain.ts, 6, 31)) ->o3.b : Symbol(b, Decl(callChain.ts, 6, 19)) ->o3 : Symbol(o3, Decl(callChain.ts, 6, 13)) ->b : Symbol(b, Decl(callChain.ts, 6, 19)) ->c : Symbol(c, Decl(callChain.ts, 6, 31)) +>o3.b?.().c : Symbol(c, Decl(callChain.ts, 16, 45)) +>o3.b : Symbol(b, Decl(callChain.ts, 16, 19)) +>o3 : Symbol(o3, Decl(callChain.ts, 16, 13)) +>b : Symbol(b, Decl(callChain.ts, 16, 19)) +>c : Symbol(c, Decl(callChain.ts, 16, 45)) + +o3.b?.(1).c; +>o3.b?.(1).c : Symbol(c, Decl(callChain.ts, 16, 45)) +>o3.b : Symbol(b, Decl(callChain.ts, 16, 19)) +>o3 : Symbol(o3, Decl(callChain.ts, 16, 13)) +>b : Symbol(b, Decl(callChain.ts, 16, 19)) +>c : Symbol(c, Decl(callChain.ts, 16, 45)) + +o3.b?.(...[1, 2]).c; +>o3.b?.(...[1, 2]).c : Symbol(c, Decl(callChain.ts, 16, 45)) +>o3.b : Symbol(b, Decl(callChain.ts, 16, 19)) +>o3 : Symbol(o3, Decl(callChain.ts, 16, 13)) +>b : Symbol(b, Decl(callChain.ts, 16, 19)) +>c : Symbol(c, Decl(callChain.ts, 16, 45)) + +o3.b?.(1, ...[2, 3], 4).c; +>o3.b?.(1, ...[2, 3], 4).c : Symbol(c, Decl(callChain.ts, 16, 45)) +>o3.b : Symbol(b, Decl(callChain.ts, 16, 19)) +>o3 : Symbol(o3, Decl(callChain.ts, 16, 13)) +>b : Symbol(b, Decl(callChain.ts, 16, 19)) +>c : Symbol(c, Decl(callChain.ts, 16, 45)) + +o3.b?.()["c"]; +>o3.b : Symbol(b, Decl(callChain.ts, 16, 19)) +>o3 : Symbol(o3, Decl(callChain.ts, 16, 13)) +>b : Symbol(b, Decl(callChain.ts, 16, 19)) + +o3.b?.(1)["c"]; +>o3.b : Symbol(b, Decl(callChain.ts, 16, 19)) +>o3 : Symbol(o3, Decl(callChain.ts, 16, 13)) +>b : Symbol(b, Decl(callChain.ts, 16, 19)) + +o3.b?.(...[1, 2])["c"]; +>o3.b : Symbol(b, Decl(callChain.ts, 16, 19)) +>o3 : Symbol(o3, Decl(callChain.ts, 16, 13)) +>b : Symbol(b, Decl(callChain.ts, 16, 19)) + +o3.b?.(1, ...[2, 3], 4)["c"]; +>o3.b : Symbol(b, Decl(callChain.ts, 16, 19)) +>o3 : Symbol(o3, Decl(callChain.ts, 16, 13)) +>b : Symbol(b, Decl(callChain.ts, 16, 19)) + +o3["b"]?.().c; +>o3["b"]?.().c : Symbol(c, Decl(callChain.ts, 16, 45)) +>o3 : Symbol(o3, Decl(callChain.ts, 16, 13)) +>"b" : Symbol(b, Decl(callChain.ts, 16, 19)) +>c : Symbol(c, Decl(callChain.ts, 16, 45)) + +o3["b"]?.(1).c; +>o3["b"]?.(1).c : Symbol(c, Decl(callChain.ts, 16, 45)) +>o3 : Symbol(o3, Decl(callChain.ts, 16, 13)) +>"b" : Symbol(b, Decl(callChain.ts, 16, 19)) +>c : Symbol(c, Decl(callChain.ts, 16, 45)) + +o3["b"]?.(...[1, 2]).c; +>o3["b"]?.(...[1, 2]).c : Symbol(c, Decl(callChain.ts, 16, 45)) +>o3 : Symbol(o3, Decl(callChain.ts, 16, 13)) +>"b" : Symbol(b, Decl(callChain.ts, 16, 19)) +>c : Symbol(c, Decl(callChain.ts, 16, 45)) + +o3["b"]?.(1, ...[2, 3], 4).c; +>o3["b"]?.(1, ...[2, 3], 4).c : Symbol(c, Decl(callChain.ts, 16, 45)) +>o3 : Symbol(o3, Decl(callChain.ts, 16, 13)) +>"b" : Symbol(b, Decl(callChain.ts, 16, 19)) +>c : Symbol(c, Decl(callChain.ts, 16, 45)) + +declare const o4: undefined | ((f: (a: T) => T) => T); +>o4 : Symbol(o4, Decl(callChain.ts, 30, 13)) +>T : Symbol(T, Decl(callChain.ts, 30, 32)) +>f : Symbol(f, Decl(callChain.ts, 30, 35)) +>a : Symbol(a, Decl(callChain.ts, 30, 39)) +>T : Symbol(T, Decl(callChain.ts, 30, 32)) +>T : Symbol(T, Decl(callChain.ts, 30, 32)) +>T : Symbol(T, Decl(callChain.ts, 30, 32)) + +declare function incr(x: number): number; +>incr : Symbol(incr, Decl(callChain.ts, 30, 57)) +>x : Symbol(x, Decl(callChain.ts, 31, 22)) + +const v: number | undefined = o4?.(incr); +>v : Symbol(v, Decl(callChain.ts, 32, 5)) +>o4 : Symbol(o4, Decl(callChain.ts, 30, 13)) +>incr : Symbol(incr, Decl(callChain.ts, 30, 57)) diff --git a/tests/baselines/reference/callChain.types b/tests/baselines/reference/callChain.types index ba476114311c7..70f835bf568c4 100644 --- a/tests/baselines/reference/callChain.types +++ b/tests/baselines/reference/callChain.types @@ -1,31 +1,257 @@ === tests/cases/conformance/expressions/optionalChaining/callChain/callChain.ts === -declare const o1: undefined | (() => number); ->o1 : (() => number) | undefined +declare const o1: undefined | ((...args: any[]) => number); +>o1 : ((...args: any[]) => number) | undefined +>args : any[] o1?.(); >o1?.() : number | undefined ->o1 : (() => number) | undefined +>o1 : ((...args: any[]) => number) | undefined -declare const o2: undefined | { b: () => number }; ->o2 : { b: () => number; } | undefined ->b : () => number +o1?.(1); +>o1?.(1) : number | undefined +>o1 : ((...args: any[]) => number) | undefined +>1 : 1 + +o1?.(...[1, 2]); +>o1?.(...[1, 2]) : number | undefined +>o1 : ((...args: any[]) => number) | undefined +>...[1, 2] : number +>[1, 2] : number[] +>1 : 1 +>2 : 2 + +o1?.(1, ...[2, 3], 4); +>o1?.(1, ...[2, 3], 4) : number | undefined +>o1 : ((...args: any[]) => number) | undefined +>1 : 1 +>...[2, 3] : number +>[2, 3] : number[] +>2 : 2 +>3 : 3 +>4 : 4 + +declare const o2: undefined | { b: (...args: any[]) => number }; +>o2 : { b: (...args: any[]) => number; } | undefined +>b : (...args: any[]) => number +>args : any[] o2?.b(); >o2?.b() : number | undefined ->o2?.b : (() => number) | undefined ->o2 : { b: () => number; } | undefined ->b : (() => number) | undefined +>o2?.b : ((...args: any[]) => number) | undefined +>o2 : { b: (...args: any[]) => number; } | undefined +>b : ((...args: any[]) => number) | undefined + +o2?.b(1); +>o2?.b(1) : number | undefined +>o2?.b : ((...args: any[]) => number) | undefined +>o2 : { b: (...args: any[]) => number; } | undefined +>b : ((...args: any[]) => number) | undefined +>1 : 1 + +o2?.b(...[1, 2]); +>o2?.b(...[1, 2]) : number | undefined +>o2?.b : ((...args: any[]) => number) | undefined +>o2 : { b: (...args: any[]) => number; } | undefined +>b : ((...args: any[]) => number) | undefined +>...[1, 2] : number +>[1, 2] : number[] +>1 : 1 +>2 : 2 + +o2?.b(1, ...[2, 3], 4); +>o2?.b(1, ...[2, 3], 4) : number | undefined +>o2?.b : ((...args: any[]) => number) | undefined +>o2 : { b: (...args: any[]) => number; } | undefined +>b : ((...args: any[]) => number) | undefined +>1 : 1 +>...[2, 3] : number +>[2, 3] : number[] +>2 : 2 +>3 : 3 +>4 : 4 + +o2?.["b"](); +>o2?.["b"]() : number | undefined +>o2?.["b"] : ((...args: any[]) => number) | undefined +>o2 : { b: (...args: any[]) => number; } | undefined +>"b" : "b" + +o2?.["b"](1); +>o2?.["b"](1) : number | undefined +>o2?.["b"] : ((...args: any[]) => number) | undefined +>o2 : { b: (...args: any[]) => number; } | undefined +>"b" : "b" +>1 : 1 -declare const o3: { b: (() => { c: string }) | undefined }; ->o3 : { b: (() => { c: string; }) | undefined; } ->b : (() => { c: string; }) | undefined +o2?.["b"](...[1, 2]); +>o2?.["b"](...[1, 2]) : number | undefined +>o2?.["b"] : ((...args: any[]) => number) | undefined +>o2 : { b: (...args: any[]) => number; } | undefined +>"b" : "b" +>...[1, 2] : number +>[1, 2] : number[] +>1 : 1 +>2 : 2 + +o2?.["b"](1, ...[2, 3], 4); +>o2?.["b"](1, ...[2, 3], 4) : number | undefined +>o2?.["b"] : ((...args: any[]) => number) | undefined +>o2 : { b: (...args: any[]) => number; } | undefined +>"b" : "b" +>1 : 1 +>...[2, 3] : number +>[2, 3] : number[] +>2 : 2 +>3 : 3 +>4 : 4 + +declare const o3: { b: ((...args: any[]) => { c: string }) | undefined }; +>o3 : { b: ((...args: any[]) => { c: string; }) | undefined; } +>b : ((...args: any[]) => { c: string; }) | undefined +>args : any[] >c : string o3.b?.().c; >o3.b?.().c : string | undefined >o3.b?.() : { c: string; } | undefined ->o3.b : (() => { c: string; }) | undefined ->o3 : { b: (() => { c: string; }) | undefined; } ->b : (() => { c: string; }) | undefined +>o3.b : ((...args: any[]) => { c: string; }) | undefined +>o3 : { b: ((...args: any[]) => { c: string; }) | undefined; } +>b : ((...args: any[]) => { c: string; }) | undefined +>c : string | undefined + +o3.b?.(1).c; +>o3.b?.(1).c : string | undefined +>o3.b?.(1) : { c: string; } | undefined +>o3.b : ((...args: any[]) => { c: string; }) | undefined +>o3 : { b: ((...args: any[]) => { c: string; }) | undefined; } +>b : ((...args: any[]) => { c: string; }) | undefined +>1 : 1 +>c : string | undefined + +o3.b?.(...[1, 2]).c; +>o3.b?.(...[1, 2]).c : string | undefined +>o3.b?.(...[1, 2]) : { c: string; } | undefined +>o3.b : ((...args: any[]) => { c: string; }) | undefined +>o3 : { b: ((...args: any[]) => { c: string; }) | undefined; } +>b : ((...args: any[]) => { c: string; }) | undefined +>...[1, 2] : number +>[1, 2] : number[] +>1 : 1 +>2 : 2 +>c : string | undefined + +o3.b?.(1, ...[2, 3], 4).c; +>o3.b?.(1, ...[2, 3], 4).c : string | undefined +>o3.b?.(1, ...[2, 3], 4) : { c: string; } | undefined +>o3.b : ((...args: any[]) => { c: string; }) | undefined +>o3 : { b: ((...args: any[]) => { c: string; }) | undefined; } +>b : ((...args: any[]) => { c: string; }) | undefined +>1 : 1 +>...[2, 3] : number +>[2, 3] : number[] +>2 : 2 +>3 : 3 +>4 : 4 +>c : string | undefined + +o3.b?.()["c"]; +>o3.b?.()["c"] : string | undefined +>o3.b?.() : { c: string; } | undefined +>o3.b : ((...args: any[]) => { c: string; }) | undefined +>o3 : { b: ((...args: any[]) => { c: string; }) | undefined; } +>b : ((...args: any[]) => { c: string; }) | undefined +>"c" : "c" + +o3.b?.(1)["c"]; +>o3.b?.(1)["c"] : string | undefined +>o3.b?.(1) : { c: string; } | undefined +>o3.b : ((...args: any[]) => { c: string; }) | undefined +>o3 : { b: ((...args: any[]) => { c: string; }) | undefined; } +>b : ((...args: any[]) => { c: string; }) | undefined +>1 : 1 +>"c" : "c" + +o3.b?.(...[1, 2])["c"]; +>o3.b?.(...[1, 2])["c"] : string | undefined +>o3.b?.(...[1, 2]) : { c: string; } | undefined +>o3.b : ((...args: any[]) => { c: string; }) | undefined +>o3 : { b: ((...args: any[]) => { c: string; }) | undefined; } +>b : ((...args: any[]) => { c: string; }) | undefined +>...[1, 2] : number +>[1, 2] : number[] +>1 : 1 +>2 : 2 +>"c" : "c" + +o3.b?.(1, ...[2, 3], 4)["c"]; +>o3.b?.(1, ...[2, 3], 4)["c"] : string | undefined +>o3.b?.(1, ...[2, 3], 4) : { c: string; } | undefined +>o3.b : ((...args: any[]) => { c: string; }) | undefined +>o3 : { b: ((...args: any[]) => { c: string; }) | undefined; } +>b : ((...args: any[]) => { c: string; }) | undefined +>1 : 1 +>...[2, 3] : number +>[2, 3] : number[] +>2 : 2 +>3 : 3 +>4 : 4 +>"c" : "c" + +o3["b"]?.().c; +>o3["b"]?.().c : string | undefined +>o3["b"]?.() : { c: string; } | undefined +>o3["b"] : ((...args: any[]) => { c: string; }) | undefined +>o3 : { b: ((...args: any[]) => { c: string; }) | undefined; } +>"b" : "b" +>c : string | undefined + +o3["b"]?.(1).c; +>o3["b"]?.(1).c : string | undefined +>o3["b"]?.(1) : { c: string; } | undefined +>o3["b"] : ((...args: any[]) => { c: string; }) | undefined +>o3 : { b: ((...args: any[]) => { c: string; }) | undefined; } +>"b" : "b" +>1 : 1 +>c : string | undefined + +o3["b"]?.(...[1, 2]).c; +>o3["b"]?.(...[1, 2]).c : string | undefined +>o3["b"]?.(...[1, 2]) : { c: string; } | undefined +>o3["b"] : ((...args: any[]) => { c: string; }) | undefined +>o3 : { b: ((...args: any[]) => { c: string; }) | undefined; } +>"b" : "b" +>...[1, 2] : number +>[1, 2] : number[] +>1 : 1 +>2 : 2 +>c : string | undefined + +o3["b"]?.(1, ...[2, 3], 4).c; +>o3["b"]?.(1, ...[2, 3], 4).c : string | undefined +>o3["b"]?.(1, ...[2, 3], 4) : { c: string; } | undefined +>o3["b"] : ((...args: any[]) => { c: string; }) | undefined +>o3 : { b: ((...args: any[]) => { c: string; }) | undefined; } +>"b" : "b" +>1 : 1 +>...[2, 3] : number +>[2, 3] : number[] +>2 : 2 +>3 : 3 +>4 : 4 >c : string | undefined +declare const o4: undefined | ((f: (a: T) => T) => T); +>o4 : ((f: (a: T) => T) => T) | undefined +>f : (a: T) => T +>a : T + +declare function incr(x: number): number; +>incr : (x: number) => number +>x : number + +const v: number | undefined = o4?.(incr); +>v : number | undefined +>o4?.(incr) : number | undefined +>o4 : ((f: (a: T) => T) => T) | undefined +>incr : (x: number) => number + diff --git a/tests/baselines/reference/elementAccessChain.js b/tests/baselines/reference/elementAccessChain.js index c6b4de76afa07..bf7804ff2cf70 100644 --- a/tests/baselines/reference/elementAccessChain.js +++ b/tests/baselines/reference/elementAccessChain.js @@ -9,13 +9,29 @@ o2?.b["c"]; declare const o3: { b: undefined | { c: string } }; o3["b"]?.c; o3.b?.["c"]; + +declare const o4: { b?: { c: { d?: { e: string } } } }; +o4.b?.["c"].d?.e; +o4.b?.["c"].d?.["e"]; + +declare const o5: { b?(): { c: { d?: { e: string } } } }; +o5.b?.()["c"].d?.e; +o5.b?.()["c"].d?.["e"]; +o5["b"]?.()["c"].d?.e; +o5["b"]?.()["c"].d?.["e"]; //// [elementAccessChain.js] "use strict"; -var _a, _b, _c, _d, _e; +var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w; (_a = o1) === null || _a === void 0 ? void 0 : _a["b"]; (_b = o2) === null || _b === void 0 ? void 0 : _b["b"].c; (_c = o2) === null || _c === void 0 ? void 0 : _c.b["c"]; (_d = o3["b"]) === null || _d === void 0 ? void 0 : _d.c; (_e = o3.b) === null || _e === void 0 ? void 0 : _e["c"]; +(_g = (_f = o4.b) === null || _f === void 0 ? void 0 : _f["c"].d) === null || _g === void 0 ? void 0 : _g.e; +(_j = (_h = o4.b) === null || _h === void 0 ? void 0 : _h["c"].d) === null || _j === void 0 ? void 0 : _j["e"]; +(_m = (_l = (_k = o5).b) === null || _l === void 0 ? void 0 : _l.call(_k)["c"].d) === null || _m === void 0 ? void 0 : _m.e; +(_q = (_p = (_o = o5).b) === null || _p === void 0 ? void 0 : _p.call(_o)["c"].d) === null || _q === void 0 ? void 0 : _q["e"]; +(_t = (_s = (_r = o5)["b"]) === null || _s === void 0 ? void 0 : _s.call(_r)["c"].d) === null || _t === void 0 ? void 0 : _t.e; +(_w = (_v = (_u = o5)["b"]) === null || _v === void 0 ? void 0 : _v.call(_u)["c"].d) === null || _w === void 0 ? void 0 : _w["e"]; diff --git a/tests/baselines/reference/elementAccessChain.symbols b/tests/baselines/reference/elementAccessChain.symbols index 1a5d7cf975312..af253ccd4bf39 100644 --- a/tests/baselines/reference/elementAccessChain.symbols +++ b/tests/baselines/reference/elementAccessChain.symbols @@ -37,3 +37,63 @@ o3.b?.["c"]; >o3 : Symbol(o3, Decl(elementAccessChain.ts, 7, 13)) >b : Symbol(b, Decl(elementAccessChain.ts, 7, 19)) +declare const o4: { b?: { c: { d?: { e: string } } } }; +>o4 : Symbol(o4, Decl(elementAccessChain.ts, 11, 13)) +>b : Symbol(b, Decl(elementAccessChain.ts, 11, 19)) +>c : Symbol(c, Decl(elementAccessChain.ts, 11, 25)) +>d : Symbol(d, Decl(elementAccessChain.ts, 11, 30)) +>e : Symbol(e, Decl(elementAccessChain.ts, 11, 36)) + +o4.b?.["c"].d?.e; +>o4.b?.["c"].d?.e : Symbol(e, Decl(elementAccessChain.ts, 11, 36)) +>o4.b?.["c"].d : Symbol(d, Decl(elementAccessChain.ts, 11, 30)) +>o4.b : Symbol(b, Decl(elementAccessChain.ts, 11, 19)) +>o4 : Symbol(o4, Decl(elementAccessChain.ts, 11, 13)) +>b : Symbol(b, Decl(elementAccessChain.ts, 11, 19)) +>d : Symbol(d, Decl(elementAccessChain.ts, 11, 30)) +>e : Symbol(e, Decl(elementAccessChain.ts, 11, 36)) + +o4.b?.["c"].d?.["e"]; +>o4.b?.["c"].d : Symbol(d, Decl(elementAccessChain.ts, 11, 30)) +>o4.b : Symbol(b, Decl(elementAccessChain.ts, 11, 19)) +>o4 : Symbol(o4, Decl(elementAccessChain.ts, 11, 13)) +>b : Symbol(b, Decl(elementAccessChain.ts, 11, 19)) +>d : Symbol(d, Decl(elementAccessChain.ts, 11, 30)) + +declare const o5: { b?(): { c: { d?: { e: string } } } }; +>o5 : Symbol(o5, Decl(elementAccessChain.ts, 15, 13)) +>b : Symbol(b, Decl(elementAccessChain.ts, 15, 19)) +>c : Symbol(c, Decl(elementAccessChain.ts, 15, 27)) +>d : Symbol(d, Decl(elementAccessChain.ts, 15, 32)) +>e : Symbol(e, Decl(elementAccessChain.ts, 15, 38)) + +o5.b?.()["c"].d?.e; +>o5.b?.()["c"].d?.e : Symbol(e, Decl(elementAccessChain.ts, 15, 38)) +>o5.b?.()["c"].d : Symbol(d, Decl(elementAccessChain.ts, 15, 32)) +>o5.b : Symbol(b, Decl(elementAccessChain.ts, 15, 19)) +>o5 : Symbol(o5, Decl(elementAccessChain.ts, 15, 13)) +>b : Symbol(b, Decl(elementAccessChain.ts, 15, 19)) +>d : Symbol(d, Decl(elementAccessChain.ts, 15, 32)) +>e : Symbol(e, Decl(elementAccessChain.ts, 15, 38)) + +o5.b?.()["c"].d?.["e"]; +>o5.b?.()["c"].d : Symbol(d, Decl(elementAccessChain.ts, 15, 32)) +>o5.b : Symbol(b, Decl(elementAccessChain.ts, 15, 19)) +>o5 : Symbol(o5, Decl(elementAccessChain.ts, 15, 13)) +>b : Symbol(b, Decl(elementAccessChain.ts, 15, 19)) +>d : Symbol(d, Decl(elementAccessChain.ts, 15, 32)) + +o5["b"]?.()["c"].d?.e; +>o5["b"]?.()["c"].d?.e : Symbol(e, Decl(elementAccessChain.ts, 15, 38)) +>o5["b"]?.()["c"].d : Symbol(d, Decl(elementAccessChain.ts, 15, 32)) +>o5 : Symbol(o5, Decl(elementAccessChain.ts, 15, 13)) +>"b" : Symbol(b, Decl(elementAccessChain.ts, 15, 19)) +>d : Symbol(d, Decl(elementAccessChain.ts, 15, 32)) +>e : Symbol(e, Decl(elementAccessChain.ts, 15, 38)) + +o5["b"]?.()["c"].d?.["e"]; +>o5["b"]?.()["c"].d : Symbol(d, Decl(elementAccessChain.ts, 15, 32)) +>o5 : Symbol(o5, Decl(elementAccessChain.ts, 15, 13)) +>"b" : Symbol(b, Decl(elementAccessChain.ts, 15, 19)) +>d : Symbol(d, Decl(elementAccessChain.ts, 15, 32)) + diff --git a/tests/baselines/reference/elementAccessChain.types b/tests/baselines/reference/elementAccessChain.types index b93246f71c01e..0d99166ba0f08 100644 --- a/tests/baselines/reference/elementAccessChain.types +++ b/tests/baselines/reference/elementAccessChain.types @@ -46,3 +46,87 @@ o3.b?.["c"]; >b : { c: string; } | undefined >"c" : "c" +declare const o4: { b?: { c: { d?: { e: string } } } }; +>o4 : { b?: { c: { d?: { e: string; } | undefined; }; } | undefined; } +>b : { c: { d?: { e: string; } | undefined; }; } | undefined +>c : { d?: { e: string; } | undefined; } +>d : { e: string; } | undefined +>e : string + +o4.b?.["c"].d?.e; +>o4.b?.["c"].d?.e : string | undefined +>o4.b?.["c"].d : { e: string; } | undefined +>o4.b?.["c"] : { d?: { e: string; } | undefined; } | undefined +>o4.b : { c: { d?: { e: string; } | undefined; }; } | undefined +>o4 : { b?: { c: { d?: { e: string; } | undefined; }; } | undefined; } +>b : { c: { d?: { e: string; } | undefined; }; } | undefined +>"c" : "c" +>d : { e: string; } | undefined +>e : string | undefined + +o4.b?.["c"].d?.["e"]; +>o4.b?.["c"].d?.["e"] : string | undefined +>o4.b?.["c"].d : { e: string; } | undefined +>o4.b?.["c"] : { d?: { e: string; } | undefined; } | undefined +>o4.b : { c: { d?: { e: string; } | undefined; }; } | undefined +>o4 : { b?: { c: { d?: { e: string; } | undefined; }; } | undefined; } +>b : { c: { d?: { e: string; } | undefined; }; } | undefined +>"c" : "c" +>d : { e: string; } | undefined +>"e" : "e" + +declare const o5: { b?(): { c: { d?: { e: string } } } }; +>o5 : { b?(): { c: { d?: { e: string; } | undefined; }; }; } +>b : (() => { c: { d?: { e: string; } | undefined; }; }) | undefined +>c : { d?: { e: string; } | undefined; } +>d : { e: string; } | undefined +>e : string + +o5.b?.()["c"].d?.e; +>o5.b?.()["c"].d?.e : string | undefined +>o5.b?.()["c"].d : { e: string; } | undefined +>o5.b?.()["c"] : { d?: { e: string; } | undefined; } | undefined +>o5.b?.() : { c: { d?: { e: string; } | undefined; }; } | undefined +>o5.b : (() => { c: { d?: { e: string; } | undefined; }; }) | undefined +>o5 : { b?(): { c: { d?: { e: string; } | undefined; }; }; } +>b : (() => { c: { d?: { e: string; } | undefined; }; }) | undefined +>"c" : "c" +>d : { e: string; } | undefined +>e : string | undefined + +o5.b?.()["c"].d?.["e"]; +>o5.b?.()["c"].d?.["e"] : string | undefined +>o5.b?.()["c"].d : { e: string; } | undefined +>o5.b?.()["c"] : { d?: { e: string; } | undefined; } | undefined +>o5.b?.() : { c: { d?: { e: string; } | undefined; }; } | undefined +>o5.b : (() => { c: { d?: { e: string; } | undefined; }; }) | undefined +>o5 : { b?(): { c: { d?: { e: string; } | undefined; }; }; } +>b : (() => { c: { d?: { e: string; } | undefined; }; }) | undefined +>"c" : "c" +>d : { e: string; } | undefined +>"e" : "e" + +o5["b"]?.()["c"].d?.e; +>o5["b"]?.()["c"].d?.e : string | undefined +>o5["b"]?.()["c"].d : { e: string; } | undefined +>o5["b"]?.()["c"] : { d?: { e: string; } | undefined; } | undefined +>o5["b"]?.() : { c: { d?: { e: string; } | undefined; }; } | undefined +>o5["b"] : (() => { c: { d?: { e: string; } | undefined; }; }) | undefined +>o5 : { b?(): { c: { d?: { e: string; } | undefined; }; }; } +>"b" : "b" +>"c" : "c" +>d : { e: string; } | undefined +>e : string | undefined + +o5["b"]?.()["c"].d?.["e"]; +>o5["b"]?.()["c"].d?.["e"] : string | undefined +>o5["b"]?.()["c"].d : { e: string; } | undefined +>o5["b"]?.()["c"] : { d?: { e: string; } | undefined; } | undefined +>o5["b"]?.() : { c: { d?: { e: string; } | undefined; }; } | undefined +>o5["b"] : (() => { c: { d?: { e: string; } | undefined; }; }) | undefined +>o5 : { b?(): { c: { d?: { e: string; } | undefined; }; }; } +>"b" : "b" +>"c" : "c" +>d : { e: string; } | undefined +>"e" : "e" + diff --git a/tests/baselines/reference/propertyAccessChain.js b/tests/baselines/reference/propertyAccessChain.js index f9ea8a78f26ba..0c15856b15cc1 100644 --- a/tests/baselines/reference/propertyAccessChain.js +++ b/tests/baselines/reference/propertyAccessChain.js @@ -7,11 +7,19 @@ o2?.b.c; declare const o3: { b: undefined | { c: string } }; o3.b?.c; + +declare const o4: { b?: { c: { d?: { e: string } } } }; +o4.b?.c.d?.e; + +declare const o5: { b?(): { c: { d?: { e: string } } } }; +o5.b?.().c.d?.e; //// [propertyAccessChain.js] "use strict"; -var _a, _b, _c; +var _a, _b, _c, _d, _e, _f, _g, _h; (_a = o1) === null || _a === void 0 ? void 0 : _a.b; (_b = o2) === null || _b === void 0 ? void 0 : _b.b.c; (_c = o3.b) === null || _c === void 0 ? void 0 : _c.c; +(_e = (_d = o4.b) === null || _d === void 0 ? void 0 : _d.c.d) === null || _e === void 0 ? void 0 : _e.e; +(_h = (_g = (_f = o5).b) === null || _g === void 0 ? void 0 : _g.call(_f).c.d) === null || _h === void 0 ? void 0 : _h.e; diff --git a/tests/baselines/reference/propertyAccessChain.symbols b/tests/baselines/reference/propertyAccessChain.symbols index 384ac9d9f05b9..8b5cea9fe67a0 100644 --- a/tests/baselines/reference/propertyAccessChain.symbols +++ b/tests/baselines/reference/propertyAccessChain.symbols @@ -32,3 +32,39 @@ o3.b?.c; >b : Symbol(b, Decl(propertyAccessChain.ts, 6, 19)) >c : Symbol(c, Decl(propertyAccessChain.ts, 6, 36)) +declare const o4: { b?: { c: { d?: { e: string } } } }; +>o4 : Symbol(o4, Decl(propertyAccessChain.ts, 9, 13)) +>b : Symbol(b, Decl(propertyAccessChain.ts, 9, 19)) +>c : Symbol(c, Decl(propertyAccessChain.ts, 9, 25)) +>d : Symbol(d, Decl(propertyAccessChain.ts, 9, 30)) +>e : Symbol(e, Decl(propertyAccessChain.ts, 9, 36)) + +o4.b?.c.d?.e; +>o4.b?.c.d?.e : Symbol(e, Decl(propertyAccessChain.ts, 9, 36)) +>o4.b?.c.d : Symbol(d, Decl(propertyAccessChain.ts, 9, 30)) +>o4.b?.c : Symbol(c, Decl(propertyAccessChain.ts, 9, 25)) +>o4.b : Symbol(b, Decl(propertyAccessChain.ts, 9, 19)) +>o4 : Symbol(o4, Decl(propertyAccessChain.ts, 9, 13)) +>b : Symbol(b, Decl(propertyAccessChain.ts, 9, 19)) +>c : Symbol(c, Decl(propertyAccessChain.ts, 9, 25)) +>d : Symbol(d, Decl(propertyAccessChain.ts, 9, 30)) +>e : Symbol(e, Decl(propertyAccessChain.ts, 9, 36)) + +declare const o5: { b?(): { c: { d?: { e: string } } } }; +>o5 : Symbol(o5, Decl(propertyAccessChain.ts, 12, 13)) +>b : Symbol(b, Decl(propertyAccessChain.ts, 12, 19)) +>c : Symbol(c, Decl(propertyAccessChain.ts, 12, 27)) +>d : Symbol(d, Decl(propertyAccessChain.ts, 12, 32)) +>e : Symbol(e, Decl(propertyAccessChain.ts, 12, 38)) + +o5.b?.().c.d?.e; +>o5.b?.().c.d?.e : Symbol(e, Decl(propertyAccessChain.ts, 12, 38)) +>o5.b?.().c.d : Symbol(d, Decl(propertyAccessChain.ts, 12, 32)) +>o5.b?.().c : Symbol(c, Decl(propertyAccessChain.ts, 12, 27)) +>o5.b : Symbol(b, Decl(propertyAccessChain.ts, 12, 19)) +>o5 : Symbol(o5, Decl(propertyAccessChain.ts, 12, 13)) +>b : Symbol(b, Decl(propertyAccessChain.ts, 12, 19)) +>c : Symbol(c, Decl(propertyAccessChain.ts, 12, 27)) +>d : Symbol(d, Decl(propertyAccessChain.ts, 12, 32)) +>e : Symbol(e, Decl(propertyAccessChain.ts, 12, 38)) + diff --git a/tests/baselines/reference/propertyAccessChain.types b/tests/baselines/reference/propertyAccessChain.types index 42122303839de..2cb030ac3f66c 100644 --- a/tests/baselines/reference/propertyAccessChain.types +++ b/tests/baselines/reference/propertyAccessChain.types @@ -32,3 +32,40 @@ o3.b?.c; >b : { c: string; } | undefined >c : string | undefined +declare const o4: { b?: { c: { d?: { e: string } } } }; +>o4 : { b?: { c: { d?: { e: string; } | undefined; }; } | undefined; } +>b : { c: { d?: { e: string; } | undefined; }; } | undefined +>c : { d?: { e: string; } | undefined; } +>d : { e: string; } | undefined +>e : string + +o4.b?.c.d?.e; +>o4.b?.c.d?.e : string | undefined +>o4.b?.c.d : { e: string; } | undefined +>o4.b?.c : { d?: { e: string; } | undefined; } | undefined +>o4.b : { c: { d?: { e: string; } | undefined; }; } | undefined +>o4 : { b?: { c: { d?: { e: string; } | undefined; }; } | undefined; } +>b : { c: { d?: { e: string; } | undefined; }; } | undefined +>c : { d?: { e: string; } | undefined; } | undefined +>d : { e: string; } | undefined +>e : string | undefined + +declare const o5: { b?(): { c: { d?: { e: string } } } }; +>o5 : { b?(): { c: { d?: { e: string; } | undefined; }; }; } +>b : (() => { c: { d?: { e: string; } | undefined; }; }) | undefined +>c : { d?: { e: string; } | undefined; } +>d : { e: string; } | undefined +>e : string + +o5.b?.().c.d?.e; +>o5.b?.().c.d?.e : string | undefined +>o5.b?.().c.d : { e: string; } | undefined +>o5.b?.().c : { d?: { e: string; } | undefined; } | undefined +>o5.b?.() : { c: { d?: { e: string; } | undefined; }; } | undefined +>o5.b : (() => { c: { d?: { e: string; } | undefined; }; }) | undefined +>o5 : { b?(): { c: { d?: { e: string; } | undefined; }; }; } +>b : (() => { c: { d?: { e: string; } | undefined; }; }) | undefined +>c : { d?: { e: string; } | undefined; } | undefined +>d : { e: string; } | undefined +>e : string | undefined + diff --git a/tests/cases/conformance/expressions/optionalChaining/callChain/callChain.3.ts b/tests/cases/conformance/expressions/optionalChaining/callChain/callChain.3.ts new file mode 100644 index 0000000000000..ee0d8078e730e --- /dev/null +++ b/tests/cases/conformance/expressions/optionalChaining/callChain/callChain.3.ts @@ -0,0 +1,12 @@ +// @strict: true + +declare function absorb(): T; +declare const a: { m?(obj: {x: T}): T } | undefined; +const n1: number = a?.m?.({x: 12 }); // should be an error (`undefined` is not assignable to `number`) +const n2: number = a?.m?.({x: absorb()}); // likewise +const n3: number | undefined = a?.m?.({x: 12}); // should be ok +const n4: number | undefined = a?.m?.({x: absorb()}); // likewise + +// Also a test showing `!` vs `?` for good measure +let t1 = a?.m?.({x: 12}); +t1 = a!.m!({x: 12}); \ No newline at end of file diff --git a/tests/cases/conformance/expressions/optionalChaining/callChain/callChain.ts b/tests/cases/conformance/expressions/optionalChaining/callChain/callChain.ts index 1e239f70628cd..fa24d920a1918 100644 --- a/tests/cases/conformance/expressions/optionalChaining/callChain/callChain.ts +++ b/tests/cases/conformance/expressions/optionalChaining/callChain/callChain.ts @@ -1,10 +1,35 @@ // @strict: true -declare const o1: undefined | (() => number); +declare const o1: undefined | ((...args: any[]) => number); o1?.(); +o1?.(1); +o1?.(...[1, 2]); +o1?.(1, ...[2, 3], 4); -declare const o2: undefined | { b: () => number }; +declare const o2: undefined | { b: (...args: any[]) => number }; o2?.b(); +o2?.b(1); +o2?.b(...[1, 2]); +o2?.b(1, ...[2, 3], 4); +o2?.["b"](); +o2?.["b"](1); +o2?.["b"](...[1, 2]); +o2?.["b"](1, ...[2, 3], 4); -declare const o3: { b: (() => { c: string }) | undefined }; +declare const o3: { b: ((...args: any[]) => { c: string }) | undefined }; o3.b?.().c; +o3.b?.(1).c; +o3.b?.(...[1, 2]).c; +o3.b?.(1, ...[2, 3], 4).c; +o3.b?.()["c"]; +o3.b?.(1)["c"]; +o3.b?.(...[1, 2])["c"]; +o3.b?.(1, ...[2, 3], 4)["c"]; +o3["b"]?.().c; +o3["b"]?.(1).c; +o3["b"]?.(...[1, 2]).c; +o3["b"]?.(1, ...[2, 3], 4).c; + +declare const o4: undefined | ((f: (a: T) => T) => T); +declare function incr(x: number): number; +const v: number | undefined = o4?.(incr); \ No newline at end of file diff --git a/tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.ts b/tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.ts index b1a9efe746d4a..6718c050a036a 100644 --- a/tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.ts +++ b/tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.ts @@ -10,3 +10,13 @@ o2?.b["c"]; declare const o3: { b: undefined | { c: string } }; o3["b"]?.c; o3.b?.["c"]; + +declare const o4: { b?: { c: { d?: { e: string } } } }; +o4.b?.["c"].d?.e; +o4.b?.["c"].d?.["e"]; + +declare const o5: { b?(): { c: { d?: { e: string } } } }; +o5.b?.()["c"].d?.e; +o5.b?.()["c"].d?.["e"]; +o5["b"]?.()["c"].d?.e; +o5["b"]?.()["c"].d?.["e"]; diff --git a/tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.ts b/tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.ts index 0514a8e1880c9..e76d95b454017 100644 --- a/tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.ts +++ b/tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.ts @@ -8,3 +8,9 @@ o2?.b.c; declare const o3: { b: undefined | { c: string } }; o3.b?.c; + +declare const o4: { b?: { c: { d?: { e: string } } } }; +o4.b?.c.d?.e; + +declare const o5: { b?(): { c: { d?: { e: string } } } }; +o5.b?.().c.d?.e; From e073c0591e6369d504eb62fb9f0834da1190ea8d Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Mon, 23 Sep 2019 20:37:31 -0700 Subject: [PATCH 05/19] Add errors for invalid assignments and a trailing '?.' --- src/compiler/checker.ts | 35 +++- src/compiler/diagnosticMessages.json | 20 ++ src/compiler/parser.ts | 3 +- .../reference/elementAccessChain.3.errors.txt | 98 +++++++++ .../reference/elementAccessChain.3.js | 75 +++++++ .../reference/elementAccessChain.3.symbols | 76 +++++++ .../reference/elementAccessChain.3.types | 190 ++++++++++++++++++ .../propertyAccessChain.3.errors.txt | 98 +++++++++ .../reference/propertyAccessChain.3.js | 75 +++++++ .../reference/propertyAccessChain.3.symbols | 76 +++++++ .../reference/propertyAccessChain.3.types | 190 ++++++++++++++++++ .../elementAccessChain.3.ts | 30 +++ .../propertyAccessChain.3.ts | 30 +++ 13 files changed, 988 insertions(+), 8 deletions(-) create mode 100644 tests/baselines/reference/elementAccessChain.3.errors.txt create mode 100644 tests/baselines/reference/elementAccessChain.3.js create mode 100644 tests/baselines/reference/elementAccessChain.3.symbols create mode 100644 tests/baselines/reference/elementAccessChain.3.types create mode 100644 tests/baselines/reference/propertyAccessChain.3.errors.txt create mode 100644 tests/baselines/reference/propertyAccessChain.3.js create mode 100644 tests/baselines/reference/propertyAccessChain.3.symbols create mode 100644 tests/baselines/reference/propertyAccessChain.3.types create mode 100644 tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.3.ts create mode 100644 tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.3.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9424976673149..7bb2c6b094f62 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -24123,13 +24123,17 @@ namespace ts { return false; } - function checkReferenceExpression(expr: Expression, invalidReferenceMessage: DiagnosticMessage): boolean { + function checkReferenceExpression(expr: Expression, invalidReferenceMessage: DiagnosticMessage, invalidOptionalChainMessage: DiagnosticMessage): boolean { // References are combinations of identifiers, parentheses, and property accesses. const node = skipOuterExpressions(expr, OuterExpressionKinds.Assertions | OuterExpressionKinds.Parentheses); if (node.kind !== SyntaxKind.Identifier && node.kind !== SyntaxKind.PropertyAccessExpression && node.kind !== SyntaxKind.ElementAccessExpression) { error(expr, invalidReferenceMessage); return false; } + if (node.flags & NodeFlags.OptionalChain) { + error(expr, invalidOptionalChainMessage); + return false; + } return true; } @@ -24239,7 +24243,10 @@ namespace ts { Diagnostics.An_arithmetic_operand_must_be_of_type_any_number_bigint_or_an_enum_type); if (ok) { // run check only if former checks succeeded to avoid reporting cascading errors - checkReferenceExpression(node.operand, Diagnostics.The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_or_a_property_access); + checkReferenceExpression( + node.operand, + Diagnostics.The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_or_a_property_access, + Diagnostics.The_operand_of_an_increment_or_decrement_operator_may_not_be_an_optional_property_access); } return getUnaryResultType(operandType); } @@ -24257,7 +24264,10 @@ namespace ts { Diagnostics.An_arithmetic_operand_must_be_of_type_any_number_bigint_or_an_enum_type); if (ok) { // run check only if former checks succeeded to avoid reporting cascading errors - checkReferenceExpression(node.operand, Diagnostics.The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_or_a_property_access); + checkReferenceExpression( + node.operand, + Diagnostics.The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_or_a_property_access, + Diagnostics.The_operand_of_an_increment_or_decrement_operator_may_not_be_an_optional_property_access); } return getUnaryResultType(operandType); } @@ -24507,7 +24517,10 @@ namespace ts { const error = target.parent.kind === SyntaxKind.SpreadAssignment ? Diagnostics.The_target_of_an_object_rest_assignment_must_be_a_variable_or_a_property_access : Diagnostics.The_left_hand_side_of_an_assignment_expression_must_be_a_variable_or_a_property_access; - if (checkReferenceExpression(target, error)) { + const optionalError = target.parent.kind === SyntaxKind.SpreadAssignment ? + Diagnostics.The_target_of_an_object_rest_assignment_may_not_be_an_optional_property_access : + Diagnostics.The_left_hand_side_of_an_assignment_expression_may_not_be_an_optional_property_access; + if (checkReferenceExpression(target, error, optionalError)) { checkTypeAssignableToAndOptionallyElaborate(sourceType, targetType, target, target); } return sourceType; @@ -24851,7 +24864,9 @@ namespace ts { // A compound assignment furthermore requires VarExpr to be classified as a reference (section 4.1) // and the type of the non-compound operation to be assignable to the type of VarExpr. - if (checkReferenceExpression(left, Diagnostics.The_left_hand_side_of_an_assignment_expression_must_be_a_variable_or_a_property_access) + if (checkReferenceExpression(left, + Diagnostics.The_left_hand_side_of_an_assignment_expression_must_be_a_variable_or_a_property_access, + Diagnostics.The_left_hand_side_of_an_assignment_expression_may_not_be_an_optional_property_access) && (!isIdentifier(left) || unescapeLeadingUnderscores(left.escapedText) !== "exports")) { // to avoid cascading errors check assignability only if 'isReference' check succeeded and no errors were reported checkTypeAssignableToAndOptionallyElaborate(valueType, leftType, left, right); @@ -28144,7 +28159,10 @@ namespace ts { } else { const leftType = checkExpression(varExpr); - checkReferenceExpression(varExpr, Diagnostics.The_left_hand_side_of_a_for_of_statement_must_be_a_variable_or_a_property_access); + checkReferenceExpression( + varExpr, + Diagnostics.The_left_hand_side_of_a_for_of_statement_must_be_a_variable_or_a_property_access, + Diagnostics.The_left_hand_side_of_a_for_of_statement_may_not_be_an_optional_property_access); // iteratedType will be undefined if the rightType was missing properties/signatures // required to get its iteratedType (like [Symbol.iterator] or next). This may be @@ -28194,7 +28212,10 @@ namespace ts { } else { // run check only former check succeeded to avoid cascading errors - checkReferenceExpression(varExpr, Diagnostics.The_left_hand_side_of_a_for_in_statement_must_be_a_variable_or_a_property_access); + checkReferenceExpression( + varExpr, + Diagnostics.The_left_hand_side_of_a_for_in_statement_must_be_a_variable_or_a_property_access, + Diagnostics.The_left_hand_side_of_a_for_in_statement_may_not_be_an_optional_property_access); } } diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 9937938dcac88..5f1bfb89e1480 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -2697,6 +2697,26 @@ "category": "Error", "code": 2773 }, + "The operand of an increment or decrement operator may not be an optional property access.": { + "category": "Error", + "code": 2774 + }, + "The target of an object rest assignment may not be an optional property access.": { + "category": "Error", + "code": 2775 + }, + "The left-hand side of an assignment expression may not be an optional property access.": { + "category": "Error", + "code": 2776 + }, + "The left-hand side of a 'for...in' statement may not be an optional property access.": { + "category": "Error", + "code": 2777 + }, + "The left-hand side of a 'for...of' statement may not be an optional property access.": { + "category": "Error", + "code": 2778 + }, "Import declaration '{0}' is using private name '{1}'.": { "category": "Error", diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 280902a29ddc0..e304be62adc92 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -4688,6 +4688,7 @@ namespace ts { function parseCallExpressionRest(expression: LeftHandSideExpression): LeftHandSideExpression { while (true) { + noop(ts); expression = parseMemberExpressionRest(expression, /*allowOptionalChain*/ true); const questionDotToken = parseOptionalToken(SyntaxKind.QuestionDotToken); @@ -4732,7 +4733,7 @@ namespace ts { const propertyAccess = createNode(SyntaxKind.PropertyAccessExpression, expression.pos) as PropertyAccessExpression; propertyAccess.expression = expression; propertyAccess.questionDotToken = questionDotToken; - propertyAccess.name = createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ false); + propertyAccess.name = createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ false, Diagnostics.Identifier_expected); propertyAccess.flags |= NodeFlags.OptionalChain; expression = finishNode(propertyAccess); } diff --git a/tests/baselines/reference/elementAccessChain.3.errors.txt b/tests/baselines/reference/elementAccessChain.3.errors.txt new file mode 100644 index 0000000000000..380da2dea4085 --- /dev/null +++ b/tests/baselines/reference/elementAccessChain.3.errors.txt @@ -0,0 +1,98 @@ +tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.3.ts(3,1): error TS2774: The operand of an increment or decrement operator may not be an optional property access. +tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.3.ts(4,1): error TS2774: The operand of an increment or decrement operator may not be an optional property access. +tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.3.ts(5,1): error TS2774: The operand of an increment or decrement operator may not be an optional property access. +tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.3.ts(6,1): error TS2774: The operand of an increment or decrement operator may not be an optional property access. +tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.3.ts(8,3): error TS2774: The operand of an increment or decrement operator may not be an optional property access. +tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.3.ts(9,3): error TS2774: The operand of an increment or decrement operator may not be an optional property access. +tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.3.ts(10,3): error TS2774: The operand of an increment or decrement operator may not be an optional property access. +tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.3.ts(11,3): error TS2774: The operand of an increment or decrement operator may not be an optional property access. +tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.3.ts(13,1): error TS2776: The left-hand side of an assignment expression may not be an optional property access. +tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.3.ts(14,1): error TS2776: The left-hand side of an assignment expression may not be an optional property access. +tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.3.ts(15,1): error TS2776: The left-hand side of an assignment expression may not be an optional property access. +tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.3.ts(16,1): error TS2776: The left-hand side of an assignment expression may not be an optional property access. +tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.3.ts(18,6): error TS2777: The left-hand side of a 'for...in' statement may not be an optional property access. +tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.3.ts(19,6): error TS2777: The left-hand side of a 'for...in' statement may not be an optional property access. +tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.3.ts(20,6): error TS2778: The left-hand side of a 'for...of' statement may not be an optional property access. +tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.3.ts(21,6): error TS2778: The left-hand side of a 'for...of' statement may not be an optional property access. +tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.3.ts(23,7): error TS2776: The left-hand side of an assignment expression may not be an optional property access. +tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.3.ts(24,7): error TS2776: The left-hand side of an assignment expression may not be an optional property access. +tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.3.ts(25,7): error TS2775: The target of an object rest assignment may not be an optional property access. +tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.3.ts(26,7): error TS2775: The target of an object rest assignment may not be an optional property access. +tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.3.ts(27,5): error TS2776: The left-hand side of an assignment expression may not be an optional property access. +tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.3.ts(28,5): error TS2776: The left-hand side of an assignment expression may not be an optional property access. + + +==== tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.3.ts (22 errors) ==== + declare const obj: any; + + obj?.["a"]++; + ~~~~~~~~~~ +!!! error TS2774: The operand of an increment or decrement operator may not be an optional property access. + obj?.a["b"]++; + ~~~~~~~~~~~ +!!! error TS2774: The operand of an increment or decrement operator may not be an optional property access. + obj?.["a"]--; + ~~~~~~~~~~ +!!! error TS2774: The operand of an increment or decrement operator may not be an optional property access. + obj?.a["b"]--; + ~~~~~~~~~~~ +!!! error TS2774: The operand of an increment or decrement operator may not be an optional property access. + + ++obj?.["a"]; + ~~~~~~~~~~ +!!! error TS2774: The operand of an increment or decrement operator may not be an optional property access. + ++obj?.a["b"]; + ~~~~~~~~~~~ +!!! error TS2774: The operand of an increment or decrement operator may not be an optional property access. + --obj?.["a"]; + ~~~~~~~~~~ +!!! error TS2774: The operand of an increment or decrement operator may not be an optional property access. + --obj?.a["b"]; + ~~~~~~~~~~~ +!!! error TS2774: The operand of an increment or decrement operator may not be an optional property access. + + obj?.["a"] = 1; + ~~~~~~~~~~ +!!! error TS2776: The left-hand side of an assignment expression may not be an optional property access. + obj?.a["b"] = 1; + ~~~~~~~~~~~ +!!! error TS2776: The left-hand side of an assignment expression may not be an optional property access. + obj?.["a"] += 1; + ~~~~~~~~~~ +!!! error TS2776: The left-hand side of an assignment expression may not be an optional property access. + obj?.a["b"] += 1; + ~~~~~~~~~~~ +!!! error TS2776: The left-hand side of an assignment expression may not be an optional property access. + + for (obj?.["a"] in {}); + ~~~~~~~~~~ +!!! error TS2777: The left-hand side of a 'for...in' statement may not be an optional property access. + for (obj?.a["b"] in {}); + ~~~~~~~~~~~ +!!! error TS2777: The left-hand side of a 'for...in' statement may not be an optional property access. + for (obj?.["a"] of []); + ~~~~~~~~~~ +!!! error TS2778: The left-hand side of a 'for...of' statement may not be an optional property access. + for (obj?.a["b"] of []); + ~~~~~~~~~~~ +!!! error TS2778: The left-hand side of a 'for...of' statement may not be an optional property access. + + ({ a: obj?.["a"] } = { a: 1 }); + ~~~~~~~~~~ +!!! error TS2776: The left-hand side of an assignment expression may not be an optional property access. + ({ a: obj?.a["b"] } = { a: 1 }); + ~~~~~~~~~~~ +!!! error TS2776: The left-hand side of an assignment expression may not be an optional property access. + ({ ...obj?.["a"] } = { a: 1 }); + ~~~~~~~~~~ +!!! error TS2775: The target of an object rest assignment may not be an optional property access. + ({ ...obj?.a["b"] } = { a: 1 }); + ~~~~~~~~~~~ +!!! error TS2775: The target of an object rest assignment may not be an optional property access. + [...obj?.["a"]] = []; + ~~~~~~~~~~ +!!! error TS2776: The left-hand side of an assignment expression may not be an optional property access. + [...obj?.a["b"]] = []; + ~~~~~~~~~~~ +!!! error TS2776: The left-hand side of an assignment expression may not be an optional property access. + \ No newline at end of file diff --git a/tests/baselines/reference/elementAccessChain.3.js b/tests/baselines/reference/elementAccessChain.3.js new file mode 100644 index 0000000000000..4d0dd250157fc --- /dev/null +++ b/tests/baselines/reference/elementAccessChain.3.js @@ -0,0 +1,75 @@ +//// [elementAccessChain.3.ts] +declare const obj: any; + +obj?.["a"]++; +obj?.a["b"]++; +obj?.["a"]--; +obj?.a["b"]--; + +++obj?.["a"]; +++obj?.a["b"]; +--obj?.["a"]; +--obj?.a["b"]; + +obj?.["a"] = 1; +obj?.a["b"] = 1; +obj?.["a"] += 1; +obj?.a["b"] += 1; + +for (obj?.["a"] in {}); +for (obj?.a["b"] in {}); +for (obj?.["a"] of []); +for (obj?.a["b"] of []); + +({ a: obj?.["a"] } = { a: 1 }); +({ a: obj?.a["b"] } = { a: 1 }); +({ ...obj?.["a"] } = { a: 1 }); +({ ...obj?.a["b"] } = { a: 1 }); +[...obj?.["a"]] = []; +[...obj?.a["b"]] = []; + + +//// [elementAccessChain.3.js] +"use strict"; +var __rest = (this && this.__rest) || function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) + t[p[i]] = s[p[i]]; + } + return t; +}; +var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x; +((_a = obj) === null || _a === void 0 ? void 0 : _a["a"])++; +((_b = obj) === null || _b === void 0 ? void 0 : _b.a["b"])++; +((_c = obj) === null || _c === void 0 ? void 0 : _c["a"])--; +((_d = obj) === null || _d === void 0 ? void 0 : _d.a["b"])--; +++((_e = obj) === null || _e === void 0 ? void 0 : _e["a"]); +++((_f = obj) === null || _f === void 0 ? void 0 : _f.a["b"]); +--((_g = obj) === null || _g === void 0 ? void 0 : _g["a"]); +--((_h = obj) === null || _h === void 0 ? void 0 : _h.a["b"]); +(_j = obj) === null || _j === void 0 ? void 0 : _j["a"] = 1; +(_k = obj) === null || _k === void 0 ? void 0 : _k.a["b"] = 1; +(_l = obj) === null || _l === void 0 ? void 0 : _l["a"] += 1; +(_m = obj) === null || _m === void 0 ? void 0 : _m.a["b"] += 1; +for ((_o = obj) === null || _o === void 0 ? void 0 : _o["a"] in {}) + ; +for ((_p = obj) === null || _p === void 0 ? void 0 : _p.a["b"] in {}) + ; +for (var _i = 0, _y = []; _i < _y.length; _i++) { + (_q = obj) === null || _q === void 0 ? void 0 : _q["a"] = _y[_i]; + ; +} +for (var _z = 0, _0 = []; _z < _0.length; _z++) { + (_r = obj) === null || _r === void 0 ? void 0 : _r.a["b"] = _0[_z]; + ; +} +((_s = obj) === null || _s === void 0 ? void 0 : _s["a"] = { a: 1 }.a); +((_t = obj) === null || _t === void 0 ? void 0 : _t.a["b"] = { a: 1 }.a); +((_u = obj) === null || _u === void 0 ? void 0 : _u["a"] = __rest({ a: 1 }, [])); +((_v = obj) === null || _v === void 0 ? void 0 : _v.a["b"] = __rest({ a: 1 }, [])); +(_w = obj) === null || _w === void 0 ? void 0 : _w["a"] = [].slice(0); +(_x = obj) === null || _x === void 0 ? void 0 : _x.a["b"] = [].slice(0); diff --git a/tests/baselines/reference/elementAccessChain.3.symbols b/tests/baselines/reference/elementAccessChain.3.symbols new file mode 100644 index 0000000000000..2b63cd9731e59 --- /dev/null +++ b/tests/baselines/reference/elementAccessChain.3.symbols @@ -0,0 +1,76 @@ +=== tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.3.ts === +declare const obj: any; +>obj : Symbol(obj, Decl(elementAccessChain.3.ts, 0, 13)) + +obj?.["a"]++; +>obj : Symbol(obj, Decl(elementAccessChain.3.ts, 0, 13)) + +obj?.a["b"]++; +>obj : Symbol(obj, Decl(elementAccessChain.3.ts, 0, 13)) + +obj?.["a"]--; +>obj : Symbol(obj, Decl(elementAccessChain.3.ts, 0, 13)) + +obj?.a["b"]--; +>obj : Symbol(obj, Decl(elementAccessChain.3.ts, 0, 13)) + +++obj?.["a"]; +>obj : Symbol(obj, Decl(elementAccessChain.3.ts, 0, 13)) + +++obj?.a["b"]; +>obj : Symbol(obj, Decl(elementAccessChain.3.ts, 0, 13)) + +--obj?.["a"]; +>obj : Symbol(obj, Decl(elementAccessChain.3.ts, 0, 13)) + +--obj?.a["b"]; +>obj : Symbol(obj, Decl(elementAccessChain.3.ts, 0, 13)) + +obj?.["a"] = 1; +>obj : Symbol(obj, Decl(elementAccessChain.3.ts, 0, 13)) + +obj?.a["b"] = 1; +>obj : Symbol(obj, Decl(elementAccessChain.3.ts, 0, 13)) + +obj?.["a"] += 1; +>obj : Symbol(obj, Decl(elementAccessChain.3.ts, 0, 13)) + +obj?.a["b"] += 1; +>obj : Symbol(obj, Decl(elementAccessChain.3.ts, 0, 13)) + +for (obj?.["a"] in {}); +>obj : Symbol(obj, Decl(elementAccessChain.3.ts, 0, 13)) + +for (obj?.a["b"] in {}); +>obj : Symbol(obj, Decl(elementAccessChain.3.ts, 0, 13)) + +for (obj?.["a"] of []); +>obj : Symbol(obj, Decl(elementAccessChain.3.ts, 0, 13)) + +for (obj?.a["b"] of []); +>obj : Symbol(obj, Decl(elementAccessChain.3.ts, 0, 13)) + +({ a: obj?.["a"] } = { a: 1 }); +>a : Symbol(a, Decl(elementAccessChain.3.ts, 22, 2)) +>obj : Symbol(obj, Decl(elementAccessChain.3.ts, 0, 13)) +>a : Symbol(a, Decl(elementAccessChain.3.ts, 22, 22)) + +({ a: obj?.a["b"] } = { a: 1 }); +>a : Symbol(a, Decl(elementAccessChain.3.ts, 23, 2)) +>obj : Symbol(obj, Decl(elementAccessChain.3.ts, 0, 13)) +>a : Symbol(a, Decl(elementAccessChain.3.ts, 23, 23)) + +({ ...obj?.["a"] } = { a: 1 }); +>obj : Symbol(obj, Decl(elementAccessChain.3.ts, 0, 13)) +>a : Symbol(a, Decl(elementAccessChain.3.ts, 24, 22)) + +({ ...obj?.a["b"] } = { a: 1 }); +>obj : Symbol(obj, Decl(elementAccessChain.3.ts, 0, 13)) +>a : Symbol(a, Decl(elementAccessChain.3.ts, 25, 23)) + +[...obj?.["a"]] = []; +>obj : Symbol(obj, Decl(elementAccessChain.3.ts, 0, 13)) + +[...obj?.a["b"]] = []; +>obj : Symbol(obj, Decl(elementAccessChain.3.ts, 0, 13)) + diff --git a/tests/baselines/reference/elementAccessChain.3.types b/tests/baselines/reference/elementAccessChain.3.types new file mode 100644 index 0000000000000..d0d00ffea7193 --- /dev/null +++ b/tests/baselines/reference/elementAccessChain.3.types @@ -0,0 +1,190 @@ +=== tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.3.ts === +declare const obj: any; +>obj : any + +obj?.["a"]++; +>obj?.["a"]++ : number +>obj?.["a"] : any +>obj : any +>"a" : "a" + +obj?.a["b"]++; +>obj?.a["b"]++ : number +>obj?.a["b"] : any +>obj?.a : any +>obj : any +>a : any +>"b" : "b" + +obj?.["a"]--; +>obj?.["a"]-- : number +>obj?.["a"] : any +>obj : any +>"a" : "a" + +obj?.a["b"]--; +>obj?.a["b"]-- : number +>obj?.a["b"] : any +>obj?.a : any +>obj : any +>a : any +>"b" : "b" + +++obj?.["a"]; +>++obj?.["a"] : number +>obj?.["a"] : any +>obj : any +>"a" : "a" + +++obj?.a["b"]; +>++obj?.a["b"] : number +>obj?.a["b"] : any +>obj?.a : any +>obj : any +>a : any +>"b" : "b" + +--obj?.["a"]; +>--obj?.["a"] : number +>obj?.["a"] : any +>obj : any +>"a" : "a" + +--obj?.a["b"]; +>--obj?.a["b"] : number +>obj?.a["b"] : any +>obj?.a : any +>obj : any +>a : any +>"b" : "b" + +obj?.["a"] = 1; +>obj?.["a"] = 1 : 1 +>obj?.["a"] : any +>obj : any +>"a" : "a" +>1 : 1 + +obj?.a["b"] = 1; +>obj?.a["b"] = 1 : 1 +>obj?.a["b"] : any +>obj?.a : any +>obj : any +>a : any +>"b" : "b" +>1 : 1 + +obj?.["a"] += 1; +>obj?.["a"] += 1 : any +>obj?.["a"] : any +>obj : any +>"a" : "a" +>1 : 1 + +obj?.a["b"] += 1; +>obj?.a["b"] += 1 : any +>obj?.a["b"] : any +>obj?.a : any +>obj : any +>a : any +>"b" : "b" +>1 : 1 + +for (obj?.["a"] in {}); +>obj?.["a"] : any +>obj : any +>"a" : "a" +>{} : {} + +for (obj?.a["b"] in {}); +>obj?.a["b"] : any +>obj?.a : any +>obj : any +>a : any +>"b" : "b" +>{} : {} + +for (obj?.["a"] of []); +>obj?.["a"] : any +>obj : any +>"a" : "a" +>[] : never[] + +for (obj?.a["b"] of []); +>obj?.a["b"] : any +>obj?.a : any +>obj : any +>a : any +>"b" : "b" +>[] : never[] + +({ a: obj?.["a"] } = { a: 1 }); +>({ a: obj?.["a"] } = { a: 1 }) : { a: number; } +>{ a: obj?.["a"] } = { a: 1 } : { a: number; } +>{ a: obj?.["a"] } : { a: any; } +>a : any +>obj?.["a"] : any +>obj : any +>"a" : "a" +>{ a: 1 } : { a: number; } +>a : number +>1 : 1 + +({ a: obj?.a["b"] } = { a: 1 }); +>({ a: obj?.a["b"] } = { a: 1 }) : { a: number; } +>{ a: obj?.a["b"] } = { a: 1 } : { a: number; } +>{ a: obj?.a["b"] } : { a: any; } +>a : any +>obj?.a["b"] : any +>obj?.a : any +>obj : any +>a : any +>"b" : "b" +>{ a: 1 } : { a: number; } +>a : number +>1 : 1 + +({ ...obj?.["a"] } = { a: 1 }); +>({ ...obj?.["a"] } = { a: 1 }) : { a: number; } +>{ ...obj?.["a"] } = { a: 1 } : { a: number; } +>{ ...obj?.["a"] } : any +>obj?.["a"] : any +>obj : any +>"a" : "a" +>{ a: 1 } : { a: number; } +>a : number +>1 : 1 + +({ ...obj?.a["b"] } = { a: 1 }); +>({ ...obj?.a["b"] } = { a: 1 }) : { a: number; } +>{ ...obj?.a["b"] } = { a: 1 } : { a: number; } +>{ ...obj?.a["b"] } : any +>obj?.a["b"] : any +>obj?.a : any +>obj : any +>a : any +>"b" : "b" +>{ a: 1 } : { a: number; } +>a : number +>1 : 1 + +[...obj?.["a"]] = []; +>[...obj?.["a"]] = [] : never[] +>[...obj?.["a"]] : never[] +>...obj?.["a"] : any +>obj?.["a"] : any +>obj : any +>"a" : "a" +>[] : never[] + +[...obj?.a["b"]] = []; +>[...obj?.a["b"]] = [] : never[] +>[...obj?.a["b"]] : never[] +>...obj?.a["b"] : any +>obj?.a["b"] : any +>obj?.a : any +>obj : any +>a : any +>"b" : "b" +>[] : never[] + diff --git a/tests/baselines/reference/propertyAccessChain.3.errors.txt b/tests/baselines/reference/propertyAccessChain.3.errors.txt new file mode 100644 index 0000000000000..b5e05c696f0c6 --- /dev/null +++ b/tests/baselines/reference/propertyAccessChain.3.errors.txt @@ -0,0 +1,98 @@ +tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.3.ts(3,1): error TS2774: The operand of an increment or decrement operator may not be an optional property access. +tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.3.ts(4,1): error TS2774: The operand of an increment or decrement operator may not be an optional property access. +tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.3.ts(5,1): error TS2774: The operand of an increment or decrement operator may not be an optional property access. +tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.3.ts(6,1): error TS2774: The operand of an increment or decrement operator may not be an optional property access. +tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.3.ts(8,3): error TS2774: The operand of an increment or decrement operator may not be an optional property access. +tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.3.ts(9,3): error TS2774: The operand of an increment or decrement operator may not be an optional property access. +tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.3.ts(10,3): error TS2774: The operand of an increment or decrement operator may not be an optional property access. +tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.3.ts(11,3): error TS2774: The operand of an increment or decrement operator may not be an optional property access. +tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.3.ts(13,1): error TS2776: The left-hand side of an assignment expression may not be an optional property access. +tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.3.ts(14,1): error TS2776: The left-hand side of an assignment expression may not be an optional property access. +tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.3.ts(15,1): error TS2776: The left-hand side of an assignment expression may not be an optional property access. +tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.3.ts(16,1): error TS2776: The left-hand side of an assignment expression may not be an optional property access. +tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.3.ts(18,6): error TS2777: The left-hand side of a 'for...in' statement may not be an optional property access. +tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.3.ts(19,6): error TS2777: The left-hand side of a 'for...in' statement may not be an optional property access. +tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.3.ts(20,6): error TS2778: The left-hand side of a 'for...of' statement may not be an optional property access. +tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.3.ts(21,6): error TS2778: The left-hand side of a 'for...of' statement may not be an optional property access. +tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.3.ts(23,7): error TS2776: The left-hand side of an assignment expression may not be an optional property access. +tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.3.ts(24,7): error TS2776: The left-hand side of an assignment expression may not be an optional property access. +tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.3.ts(25,7): error TS2775: The target of an object rest assignment may not be an optional property access. +tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.3.ts(26,7): error TS2775: The target of an object rest assignment may not be an optional property access. +tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.3.ts(27,5): error TS2776: The left-hand side of an assignment expression may not be an optional property access. +tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.3.ts(28,5): error TS2776: The left-hand side of an assignment expression may not be an optional property access. + + +==== tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.3.ts (22 errors) ==== + declare const obj: any; + + obj?.a++; + ~~~~~~ +!!! error TS2774: The operand of an increment or decrement operator may not be an optional property access. + obj?.a.b++; + ~~~~~~~~ +!!! error TS2774: The operand of an increment or decrement operator may not be an optional property access. + obj?.a--; + ~~~~~~ +!!! error TS2774: The operand of an increment or decrement operator may not be an optional property access. + obj?.a.b--; + ~~~~~~~~ +!!! error TS2774: The operand of an increment or decrement operator may not be an optional property access. + + ++obj?.a; + ~~~~~~ +!!! error TS2774: The operand of an increment or decrement operator may not be an optional property access. + ++obj?.a.b; + ~~~~~~~~ +!!! error TS2774: The operand of an increment or decrement operator may not be an optional property access. + --obj?.a; + ~~~~~~ +!!! error TS2774: The operand of an increment or decrement operator may not be an optional property access. + --obj?.a.b; + ~~~~~~~~ +!!! error TS2774: The operand of an increment or decrement operator may not be an optional property access. + + obj?.a = 1; + ~~~~~~ +!!! error TS2776: The left-hand side of an assignment expression may not be an optional property access. + obj?.a.b = 1; + ~~~~~~~~ +!!! error TS2776: The left-hand side of an assignment expression may not be an optional property access. + obj?.a += 1; + ~~~~~~ +!!! error TS2776: The left-hand side of an assignment expression may not be an optional property access. + obj?.a.b += 1; + ~~~~~~~~ +!!! error TS2776: The left-hand side of an assignment expression may not be an optional property access. + + for (obj?.a in {}); + ~~~~~~ +!!! error TS2777: The left-hand side of a 'for...in' statement may not be an optional property access. + for (obj?.a.b in {}); + ~~~~~~~~ +!!! error TS2777: The left-hand side of a 'for...in' statement may not be an optional property access. + for (obj?.a of []); + ~~~~~~ +!!! error TS2778: The left-hand side of a 'for...of' statement may not be an optional property access. + for (obj?.a.b of []); + ~~~~~~~~ +!!! error TS2778: The left-hand side of a 'for...of' statement may not be an optional property access. + + ({ a: obj?.a } = { a: 1 }); + ~~~~~~ +!!! error TS2776: The left-hand side of an assignment expression may not be an optional property access. + ({ a: obj?.a.b } = { a: 1 }); + ~~~~~~~~ +!!! error TS2776: The left-hand side of an assignment expression may not be an optional property access. + ({ ...obj?.a } = { a: 1 }); + ~~~~~~ +!!! error TS2775: The target of an object rest assignment may not be an optional property access. + ({ ...obj?.a.b } = { a: 1 }); + ~~~~~~~~ +!!! error TS2775: The target of an object rest assignment may not be an optional property access. + [...obj?.a] = []; + ~~~~~~ +!!! error TS2776: The left-hand side of an assignment expression may not be an optional property access. + [...obj?.a.b] = []; + ~~~~~~~~ +!!! error TS2776: The left-hand side of an assignment expression may not be an optional property access. + \ No newline at end of file diff --git a/tests/baselines/reference/propertyAccessChain.3.js b/tests/baselines/reference/propertyAccessChain.3.js new file mode 100644 index 0000000000000..3eedf7c2b222f --- /dev/null +++ b/tests/baselines/reference/propertyAccessChain.3.js @@ -0,0 +1,75 @@ +//// [propertyAccessChain.3.ts] +declare const obj: any; + +obj?.a++; +obj?.a.b++; +obj?.a--; +obj?.a.b--; + +++obj?.a; +++obj?.a.b; +--obj?.a; +--obj?.a.b; + +obj?.a = 1; +obj?.a.b = 1; +obj?.a += 1; +obj?.a.b += 1; + +for (obj?.a in {}); +for (obj?.a.b in {}); +for (obj?.a of []); +for (obj?.a.b of []); + +({ a: obj?.a } = { a: 1 }); +({ a: obj?.a.b } = { a: 1 }); +({ ...obj?.a } = { a: 1 }); +({ ...obj?.a.b } = { a: 1 }); +[...obj?.a] = []; +[...obj?.a.b] = []; + + +//// [propertyAccessChain.3.js] +"use strict"; +var __rest = (this && this.__rest) || function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) + t[p[i]] = s[p[i]]; + } + return t; +}; +var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x; +((_a = obj) === null || _a === void 0 ? void 0 : _a.a)++; +((_b = obj) === null || _b === void 0 ? void 0 : _b.a.b)++; +((_c = obj) === null || _c === void 0 ? void 0 : _c.a)--; +((_d = obj) === null || _d === void 0 ? void 0 : _d.a.b)--; +++((_e = obj) === null || _e === void 0 ? void 0 : _e.a); +++((_f = obj) === null || _f === void 0 ? void 0 : _f.a.b); +--((_g = obj) === null || _g === void 0 ? void 0 : _g.a); +--((_h = obj) === null || _h === void 0 ? void 0 : _h.a.b); +(_j = obj) === null || _j === void 0 ? void 0 : _j.a = 1; +(_k = obj) === null || _k === void 0 ? void 0 : _k.a.b = 1; +(_l = obj) === null || _l === void 0 ? void 0 : _l.a += 1; +(_m = obj) === null || _m === void 0 ? void 0 : _m.a.b += 1; +for ((_o = obj) === null || _o === void 0 ? void 0 : _o.a in {}) + ; +for ((_p = obj) === null || _p === void 0 ? void 0 : _p.a.b in {}) + ; +for (var _i = 0, _y = []; _i < _y.length; _i++) { + (_q = obj) === null || _q === void 0 ? void 0 : _q.a = _y[_i]; + ; +} +for (var _z = 0, _0 = []; _z < _0.length; _z++) { + (_r = obj) === null || _r === void 0 ? void 0 : _r.a.b = _0[_z]; + ; +} +((_s = obj) === null || _s === void 0 ? void 0 : _s.a = { a: 1 }.a); +((_t = obj) === null || _t === void 0 ? void 0 : _t.a.b = { a: 1 }.a); +((_u = obj) === null || _u === void 0 ? void 0 : _u.a = __rest({ a: 1 }, [])); +((_v = obj) === null || _v === void 0 ? void 0 : _v.a.b = __rest({ a: 1 }, [])); +(_w = obj) === null || _w === void 0 ? void 0 : _w.a = [].slice(0); +(_x = obj) === null || _x === void 0 ? void 0 : _x.a.b = [].slice(0); diff --git a/tests/baselines/reference/propertyAccessChain.3.symbols b/tests/baselines/reference/propertyAccessChain.3.symbols new file mode 100644 index 0000000000000..348a436e00781 --- /dev/null +++ b/tests/baselines/reference/propertyAccessChain.3.symbols @@ -0,0 +1,76 @@ +=== tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.3.ts === +declare const obj: any; +>obj : Symbol(obj, Decl(propertyAccessChain.3.ts, 0, 13)) + +obj?.a++; +>obj : Symbol(obj, Decl(propertyAccessChain.3.ts, 0, 13)) + +obj?.a.b++; +>obj : Symbol(obj, Decl(propertyAccessChain.3.ts, 0, 13)) + +obj?.a--; +>obj : Symbol(obj, Decl(propertyAccessChain.3.ts, 0, 13)) + +obj?.a.b--; +>obj : Symbol(obj, Decl(propertyAccessChain.3.ts, 0, 13)) + +++obj?.a; +>obj : Symbol(obj, Decl(propertyAccessChain.3.ts, 0, 13)) + +++obj?.a.b; +>obj : Symbol(obj, Decl(propertyAccessChain.3.ts, 0, 13)) + +--obj?.a; +>obj : Symbol(obj, Decl(propertyAccessChain.3.ts, 0, 13)) + +--obj?.a.b; +>obj : Symbol(obj, Decl(propertyAccessChain.3.ts, 0, 13)) + +obj?.a = 1; +>obj : Symbol(obj, Decl(propertyAccessChain.3.ts, 0, 13)) + +obj?.a.b = 1; +>obj : Symbol(obj, Decl(propertyAccessChain.3.ts, 0, 13)) + +obj?.a += 1; +>obj : Symbol(obj, Decl(propertyAccessChain.3.ts, 0, 13)) + +obj?.a.b += 1; +>obj : Symbol(obj, Decl(propertyAccessChain.3.ts, 0, 13)) + +for (obj?.a in {}); +>obj : Symbol(obj, Decl(propertyAccessChain.3.ts, 0, 13)) + +for (obj?.a.b in {}); +>obj : Symbol(obj, Decl(propertyAccessChain.3.ts, 0, 13)) + +for (obj?.a of []); +>obj : Symbol(obj, Decl(propertyAccessChain.3.ts, 0, 13)) + +for (obj?.a.b of []); +>obj : Symbol(obj, Decl(propertyAccessChain.3.ts, 0, 13)) + +({ a: obj?.a } = { a: 1 }); +>a : Symbol(a, Decl(propertyAccessChain.3.ts, 22, 2)) +>obj : Symbol(obj, Decl(propertyAccessChain.3.ts, 0, 13)) +>a : Symbol(a, Decl(propertyAccessChain.3.ts, 22, 18)) + +({ a: obj?.a.b } = { a: 1 }); +>a : Symbol(a, Decl(propertyAccessChain.3.ts, 23, 2)) +>obj : Symbol(obj, Decl(propertyAccessChain.3.ts, 0, 13)) +>a : Symbol(a, Decl(propertyAccessChain.3.ts, 23, 20)) + +({ ...obj?.a } = { a: 1 }); +>obj : Symbol(obj, Decl(propertyAccessChain.3.ts, 0, 13)) +>a : Symbol(a, Decl(propertyAccessChain.3.ts, 24, 18)) + +({ ...obj?.a.b } = { a: 1 }); +>obj : Symbol(obj, Decl(propertyAccessChain.3.ts, 0, 13)) +>a : Symbol(a, Decl(propertyAccessChain.3.ts, 25, 20)) + +[...obj?.a] = []; +>obj : Symbol(obj, Decl(propertyAccessChain.3.ts, 0, 13)) + +[...obj?.a.b] = []; +>obj : Symbol(obj, Decl(propertyAccessChain.3.ts, 0, 13)) + diff --git a/tests/baselines/reference/propertyAccessChain.3.types b/tests/baselines/reference/propertyAccessChain.3.types new file mode 100644 index 0000000000000..a5b97624e2475 --- /dev/null +++ b/tests/baselines/reference/propertyAccessChain.3.types @@ -0,0 +1,190 @@ +=== tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.3.ts === +declare const obj: any; +>obj : any + +obj?.a++; +>obj?.a++ : number +>obj?.a : any +>obj : any +>a : any + +obj?.a.b++; +>obj?.a.b++ : number +>obj?.a.b : any +>obj?.a : any +>obj : any +>a : any +>b : any + +obj?.a--; +>obj?.a-- : number +>obj?.a : any +>obj : any +>a : any + +obj?.a.b--; +>obj?.a.b-- : number +>obj?.a.b : any +>obj?.a : any +>obj : any +>a : any +>b : any + +++obj?.a; +>++obj?.a : number +>obj?.a : any +>obj : any +>a : any + +++obj?.a.b; +>++obj?.a.b : number +>obj?.a.b : any +>obj?.a : any +>obj : any +>a : any +>b : any + +--obj?.a; +>--obj?.a : number +>obj?.a : any +>obj : any +>a : any + +--obj?.a.b; +>--obj?.a.b : number +>obj?.a.b : any +>obj?.a : any +>obj : any +>a : any +>b : any + +obj?.a = 1; +>obj?.a = 1 : 1 +>obj?.a : any +>obj : any +>a : any +>1 : 1 + +obj?.a.b = 1; +>obj?.a.b = 1 : 1 +>obj?.a.b : any +>obj?.a : any +>obj : any +>a : any +>b : any +>1 : 1 + +obj?.a += 1; +>obj?.a += 1 : any +>obj?.a : any +>obj : any +>a : any +>1 : 1 + +obj?.a.b += 1; +>obj?.a.b += 1 : any +>obj?.a.b : any +>obj?.a : any +>obj : any +>a : any +>b : any +>1 : 1 + +for (obj?.a in {}); +>obj?.a : any +>obj : any +>a : any +>{} : {} + +for (obj?.a.b in {}); +>obj?.a.b : any +>obj?.a : any +>obj : any +>a : any +>b : any +>{} : {} + +for (obj?.a of []); +>obj?.a : any +>obj : any +>a : any +>[] : never[] + +for (obj?.a.b of []); +>obj?.a.b : any +>obj?.a : any +>obj : any +>a : any +>b : any +>[] : never[] + +({ a: obj?.a } = { a: 1 }); +>({ a: obj?.a } = { a: 1 }) : { a: number; } +>{ a: obj?.a } = { a: 1 } : { a: number; } +>{ a: obj?.a } : { a: any; } +>a : any +>obj?.a : any +>obj : any +>a : any +>{ a: 1 } : { a: number; } +>a : number +>1 : 1 + +({ a: obj?.a.b } = { a: 1 }); +>({ a: obj?.a.b } = { a: 1 }) : { a: number; } +>{ a: obj?.a.b } = { a: 1 } : { a: number; } +>{ a: obj?.a.b } : { a: any; } +>a : any +>obj?.a.b : any +>obj?.a : any +>obj : any +>a : any +>b : any +>{ a: 1 } : { a: number; } +>a : number +>1 : 1 + +({ ...obj?.a } = { a: 1 }); +>({ ...obj?.a } = { a: 1 }) : { a: number; } +>{ ...obj?.a } = { a: 1 } : { a: number; } +>{ ...obj?.a } : any +>obj?.a : any +>obj : any +>a : any +>{ a: 1 } : { a: number; } +>a : number +>1 : 1 + +({ ...obj?.a.b } = { a: 1 }); +>({ ...obj?.a.b } = { a: 1 }) : { a: number; } +>{ ...obj?.a.b } = { a: 1 } : { a: number; } +>{ ...obj?.a.b } : any +>obj?.a.b : any +>obj?.a : any +>obj : any +>a : any +>b : any +>{ a: 1 } : { a: number; } +>a : number +>1 : 1 + +[...obj?.a] = []; +>[...obj?.a] = [] : never[] +>[...obj?.a] : never[] +>...obj?.a : any +>obj?.a : any +>obj : any +>a : any +>[] : never[] + +[...obj?.a.b] = []; +>[...obj?.a.b] = [] : never[] +>[...obj?.a.b] : never[] +>...obj?.a.b : any +>obj?.a.b : any +>obj?.a : any +>obj : any +>a : any +>b : any +>[] : never[] + diff --git a/tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.3.ts b/tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.3.ts new file mode 100644 index 0000000000000..b494ac968e9be --- /dev/null +++ b/tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.3.ts @@ -0,0 +1,30 @@ +// @strict: true + +declare const obj: any; + +obj?.["a"]++; +obj?.a["b"]++; +obj?.["a"]--; +obj?.a["b"]--; + +++obj?.["a"]; +++obj?.a["b"]; +--obj?.["a"]; +--obj?.a["b"]; + +obj?.["a"] = 1; +obj?.a["b"] = 1; +obj?.["a"] += 1; +obj?.a["b"] += 1; + +for (obj?.["a"] in {}); +for (obj?.a["b"] in {}); +for (obj?.["a"] of []); +for (obj?.a["b"] of []); + +({ a: obj?.["a"] } = { a: 1 }); +({ a: obj?.a["b"] } = { a: 1 }); +({ ...obj?.["a"] } = { a: 1 }); +({ ...obj?.a["b"] } = { a: 1 }); +[...obj?.["a"]] = []; +[...obj?.a["b"]] = []; diff --git a/tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.3.ts b/tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.3.ts new file mode 100644 index 0000000000000..82c96bbf82f1f --- /dev/null +++ b/tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.3.ts @@ -0,0 +1,30 @@ +// @strict: true + +declare const obj: any; + +obj?.a++; +obj?.a.b++; +obj?.a--; +obj?.a.b--; + +++obj?.a; +++obj?.a.b; +--obj?.a; +--obj?.a.b; + +obj?.a = 1; +obj?.a.b = 1; +obj?.a += 1; +obj?.a.b += 1; + +for (obj?.a in {}); +for (obj?.a.b in {}); +for (obj?.a of []); +for (obj?.a.b of []); + +({ a: obj?.a } = { a: 1 }); +({ a: obj?.a.b } = { a: 1 }); +({ ...obj?.a } = { a: 1 }); +({ ...obj?.a.b } = { a: 1 }); +[...obj?.a] = []; +[...obj?.a.b] = []; From 72f44d943928a4368839e83691bf9a177ae75981 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Mon, 23 Sep 2019 20:44:45 -0700 Subject: [PATCH 06/19] Add additional signature help test, fix lint warnings --- src/compiler/checker.ts | 2 +- src/compiler/factory.ts | 4 ++-- .../cases/fourslash/signatureHelpOptionalCall2.ts | 14 ++++++++++++++ 3 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 tests/cases/fourslash/signatureHelpOptionalCall2.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7bb2c6b094f62..a7db82982f0f9 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -24864,7 +24864,7 @@ namespace ts { // A compound assignment furthermore requires VarExpr to be classified as a reference (section 4.1) // and the type of the non-compound operation to be assignable to the type of VarExpr. - if (checkReferenceExpression(left, + if (checkReferenceExpression(left, Diagnostics.The_left_hand_side_of_an_assignment_expression_must_be_a_variable_or_a_property_access, Diagnostics.The_left_hand_side_of_an_assignment_expression_may_not_be_an_optional_property_access) && (!isIdentifier(left) || unescapeLeadingUnderscores(left.escapedText) !== "exports")) { diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index bdfdf4d6be432..d586b3de298dc 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -1094,7 +1094,7 @@ namespace ts { : node; } - export function createCallChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, typeArguments: ReadonlyArray | undefined, argumentsArray: ReadonlyArray | undefined) { + export function createCallChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[] | undefined) { const node = createSynthesizedNode(SyntaxKind.CallExpression); node.flags |= NodeFlags.OptionalChain; node.expression = parenthesizeForAccess(expression); @@ -1104,7 +1104,7 @@ namespace ts { return node; } - export function updateCallChain(node: CallChain, expression: Expression, questionDotToken: QuestionDotToken | undefined, typeArguments: ReadonlyArray | undefined, argumentsArray: ReadonlyArray) { + export function updateCallChain(node: CallChain, expression: Expression, questionDotToken: QuestionDotToken | undefined, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[]) { Debug.assert(!!(node.flags & NodeFlags.OptionalChain), "Cannot update a CallExpression using updateCallChain. Use updateCall instead."); return node.expression !== expression || node.questionDotToken !== questionDotToken diff --git a/tests/cases/fourslash/signatureHelpOptionalCall2.ts b/tests/cases/fourslash/signatureHelpOptionalCall2.ts new file mode 100644 index 0000000000000..ab388398177a0 --- /dev/null +++ b/tests/cases/fourslash/signatureHelpOptionalCall2.ts @@ -0,0 +1,14 @@ +/// + +////declare const fnTest: undefined | ((str: string, num: number) => void); +////fnTest?.(/*1*/); + +verify.signatureHelp( + { + marker: "1", + text: 'fnTest(str: string, num: number): void', + parameterCount: 2, + parameterName: "str", + parameterSpan: "str: string", + }, +); From 096bb4901b616a684aeb32fd7a798e4e1ca9acba Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Tue, 24 Sep 2019 15:16:09 -0700 Subject: [PATCH 07/19] Fix to insert text for completions --- src/compiler/parser.ts | 1 - src/services/completions.ts | 40 +++++++++++++++++++++++-------------- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 1fb3d2d0248ff..e38a0c7d1f9a0 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -4701,7 +4701,6 @@ namespace ts { function parseCallExpressionRest(expression: LeftHandSideExpression): LeftHandSideExpression { while (true) { - noop(ts); expression = parseMemberExpressionRest(expression, /*allowOptionalChain*/ true); const questionDotToken = parseOptionalToken(SyntaxKind.QuestionDotToken); diff --git a/src/services/completions.ts b/src/services/completions.ts index f268bb20b73c0..c82ef7dbd357d 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -12,12 +12,14 @@ namespace ts.Completions { export type Log = (message: string) => void; const enum SymbolOriginInfoKind { - ThisType = 1 << 0, - SymbolMemberNoExport = 1 << 1, - SymbolMemberExport = 1 << 2, - Nullable = 1 << 3, - Export = 1 << 4, - Promise = 1 << 4 + ThisType = 1 << 0, + SymbolMember = 1 << 1, + Export = 1 << 2, + Promise = 1 << 3, + Nullable = 1 << 4, + + SymbolMemberNoExport = SymbolMember, + SymbolMemberExport = SymbolMember | Export, } interface SymbolOriginInfo { @@ -29,15 +31,23 @@ namespace ts.Completions { moduleSymbol: Symbol; isDefaultExport: boolean; } + + function originIsThisType(origin: SymbolOriginInfo): boolean { + return !!(origin.kind & SymbolOriginInfoKind.ThisType); + } + function originIsSymbolMember(origin: SymbolOriginInfo): boolean { - return !!(origin.kind & SymbolOriginInfoKind.SymbolMemberExport || origin.kind & SymbolOriginInfoKind.SymbolMemberNoExport); + return !!(origin.kind & SymbolOriginInfoKind.SymbolMember); } + function originIsExport(origin: SymbolOriginInfo): origin is SymbolOriginInfoExport { - return !!(origin.kind & SymbolOriginInfoKind.SymbolMemberExport || origin.kind & SymbolOriginInfoKind.Export); + return !!(origin.kind & SymbolOriginInfoKind.Export); } + function originIsPromise(origin: SymbolOriginInfo): boolean { return !!(origin.kind & SymbolOriginInfoKind.Promise); } + function originIsNullableMember(origin: SymbolOriginInfo): boolean { return !!(origin.kind & SymbolOriginInfoKind.Nullable); } @@ -265,7 +275,7 @@ namespace ts.Completions { let replacementSpan: TextSpan | undefined; const insertQuestionDot = origin && originIsNullableMember(origin); const useBraces = origin && originIsSymbolMember(origin) || needsConvertPropertyAccess; - if (origin && origin.kind & SymbolOriginInfoKind.ThisType) { + if (origin && originIsThisType(origin)) { insertText = needsConvertPropertyAccess ? `this${insertQuestionDot ? "?." : ""}[${quote(name, preferences)}]` : `this${insertQuestionDot ? "?." : "."}${name}`; @@ -274,7 +284,7 @@ namespace ts.Completions { // Somehow there was a global with a non-identifier name. Hopefully someone will complain about getting a "foo bar" global completion and provide a repro. else if ((useBraces || insertQuestionDot) && propertyAccessToConvert) { insertText = useBraces ? needsConvertPropertyAccess ? `[${quote(name, preferences)}]` : `[${name}]` : name; - if (insertQuestionDot) { + if (insertQuestionDot || propertyAccessToConvert.questionDotToken) { insertText = `?.${insertText}`; } @@ -794,7 +804,6 @@ namespace ts.Completions { let propertyAccessToConvert: PropertyAccessExpression | undefined; let isRightOfDot = false; let isRightOfQuestionDot = false; - let isOptional = false; let isRightOfOpenTag = false; let isStartingCloseTag = false; let isJsxInitializer: IsJsxInitializer = false; @@ -813,7 +822,6 @@ namespace ts.Completions { isRightOfQuestionDot = contextToken.kind === SyntaxKind.QuestionDotToken; switch (parent.kind) { case SyntaxKind.PropertyAccessExpression: - isOptional = isOptionalChain(parent); propertyAccessToConvert = parent as PropertyAccessExpression; node = propertyAccessToConvert.expression; if (node.end === contextToken.pos && @@ -922,7 +930,6 @@ namespace ts.Completions { } else if (isRightOfQuestionDot) { getTypeScriptMemberSymbols(); - isNewIdentifierLocation = true; } else if (isRightOfOpenTag) { const tagSymbols = Debug.assertEachDefined(typeChecker.getJsxIntrinsicTagNamesAt(location), "getJsxIntrinsicTagNames() should all be defined"); @@ -1029,7 +1036,7 @@ namespace ts.Completions { if (!isTypeLocation && symbol.declarations && symbol.declarations.some(d => d.kind !== SyntaxKind.SourceFile && d.kind !== SyntaxKind.ModuleDeclaration && d.kind !== SyntaxKind.EnumDeclaration)) { - const type = removeOptionality(typeChecker.getTypeOfSymbolAtLocation(symbol, node), isRightOfQuestionDot, isOptional); + const type = typeChecker.getTypeOfSymbolAtLocation(symbol, node).getNonOptionalType(); const nonNullType = type.getNonNullableType(); const insertQuestionDot = isRightOfDot && !isRightOfQuestionDot && type !== nonNullType; addTypeProperties(nonNullType, !!(node.flags & NodeFlags.AwaitContext), insertQuestionDot); @@ -1047,7 +1054,7 @@ namespace ts.Completions { } if (!isTypeLocation) { - const type = removeOptionality(typeChecker.getTypeAtLocation(node), isRightOfQuestionDot, isOptional); + const type = typeChecker.getTypeAtLocation(node).getNonOptionalType(); const nonNullType = type.getNonNullableType(); const insertQuestionDot = isRightOfDot && !isRightOfQuestionDot && type !== nonNullType; addTypeProperties(nonNullType, !!(node.flags & NodeFlags.AwaitContext), insertQuestionDot); @@ -1056,6 +1063,9 @@ namespace ts.Completions { function addTypeProperties(type: Type, insertAwait: boolean, insertQuestionDot: boolean): void { isNewIdentifierLocation = !!type.getStringIndexType(); + if (isRightOfQuestionDot && some(type.getCallSignatures())) { + isNewIdentifierLocation = true; + } const propertyAccess = node.kind === SyntaxKind.ImportType ? node : node.parent; if (isUncheckedFile) { From 1bf2d5644f257efe305d066681fc6da483d250f3 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 25 Sep 2019 15:06:45 -0700 Subject: [PATCH 08/19] Add initial control-flow analysis for optional chains --- src/compiler/binder.ts | 97 +++++- src/compiler/checker.ts | 50 ++- src/compiler/types.ts | 76 ++++- src/compiler/utilities.ts | 18 ++ .../reference/api/tsserverlibrary.d.ts | 11 +- tests/baselines/reference/api/typescript.d.ts | 11 +- .../controlFlowOptionalChain.errors.txt | 125 ++++++++ .../reference/controlFlowOptionalChain.js | 182 +++++++++++ .../controlFlowOptionalChain.symbols | 273 ++++++++++++++++ .../reference/controlFlowOptionalChain.types | 303 ++++++++++++++++++ .../controlFlow/controlFlowOptionalChain.ts | 109 +++++++ 11 files changed, 1233 insertions(+), 22 deletions(-) create mode 100644 tests/baselines/reference/controlFlowOptionalChain.errors.txt create mode 100644 tests/baselines/reference/controlFlowOptionalChain.js create mode 100644 tests/baselines/reference/controlFlowOptionalChain.symbols create mode 100644 tests/baselines/reference/controlFlowOptionalChain.types create mode 100644 tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 9ed01f65631be..6c3d9113dd249 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -132,6 +132,8 @@ namespace ts { let currentReturnTarget: FlowLabel | undefined; let currentTrueTarget: FlowLabel | undefined; let currentFalseTarget: FlowLabel | undefined; + let currentPresentTarget: FlowLabel | undefined; + let currentMissingTarget: FlowLabel | undefined; let preSwitchCaseFlow: FlowNode | undefined; let activeLabels: ActiveLabel[] | undefined; let hasExplicitReturn: boolean; @@ -200,6 +202,8 @@ namespace ts { currentReturnTarget = undefined; currentTrueTarget = undefined; currentFalseTarget = undefined; + currentPresentTarget = undefined; + currentMissingTarget = undefined; activeLabels = undefined!; hasExplicitReturn = false; emitFlags = NodeFlags.None; @@ -736,6 +740,10 @@ namespace ts { case SyntaxKind.VariableDeclaration: bindVariableDeclarationFlow(node); break; + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + bindAccessExpressionFlow(node); + break; case SyntaxKind.CallExpression: bindCallExpressionFlow(node); break; @@ -889,6 +897,14 @@ namespace ts { return flowNodeCreated({ flags, antecedent, node: expression }); } + function createFlowOptionalChain(flags: FlowFlags, antecedent: FlowNode, expression: Expression): FlowNode { + if (antecedent.flags & FlowFlags.Unreachable) { + return antecedent; + } + setFlowNodeReferenced(antecedent); + return flowNodeCreated({ flags, antecedent, node: expression }); + } + function createFlowSwitchClause(antecedent: FlowNode, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number): FlowNode { if (!isNarrowingExpression(switchStatement.expression)) { return antecedent; @@ -1481,22 +1497,79 @@ namespace ts { } } - function bindCallExpressionFlow(node: CallExpression) { - // If the target of the call expression is a function expression or arrow function we have - // an immediately invoked function expression (IIFE). Initialize the flowNode property to - // the current control flow (which includes evaluation of the IIFE arguments). - let expr: Expression = node.expression; - while (expr.kind === SyntaxKind.ParenthesizedExpression) { - expr = (expr).expression; - } - if (expr.kind === SyntaxKind.FunctionExpression || expr.kind === SyntaxKind.ArrowFunction) { - bindEach(node.typeArguments); - bindEach(node.arguments); - bind(node.expression); + function bindOptionalExpression(node: Expression, presentTarget: FlowLabel, missingTarget: FlowLabel) { + const savedPresentTarget = currentPresentTarget; + const savedMissingTarget = currentMissingTarget; + currentPresentTarget = presentTarget; + currentMissingTarget = missingTarget; + bind(node); + currentPresentTarget = savedPresentTarget; + currentMissingTarget = savedMissingTarget; + if (!isValidOptionalChain(node)) { + addAntecedent(presentTarget, createFlowOptionalChain(FlowFlags.Present, currentFlow, node)); + addAntecedent(missingTarget, createFlowOptionalChain(FlowFlags.Missing, currentFlow, node)); + } + } + + function isOutermostOptionalChain(node: ValidOptionalChain) { + return !isOptionalChain(node.parent) || node.parent.expression !== node; + } + + function bindOptionalChainFlow(node: ValidOptionalChain) { + const postExpressionLabel = isOutermostOptionalChain(node) ? createBranchLabel() : undefined; + const presentTarget = postExpressionLabel ? createBranchLabel() : Debug.assertDefined(currentPresentTarget); + const missingTarget = postExpressionLabel ? createBranchLabel() : Debug.assertDefined(currentMissingTarget); + bindOptionalExpression(node.expression, presentTarget, missingTarget); + if (!isValidOptionalChain(node.expression)) { + currentFlow = finishFlowLabel(presentTarget); + } + bind(node.questionDotToken); + switch (node.kind) { + case SyntaxKind.PropertyAccessExpression: + bind(node.name); + break; + case SyntaxKind.ElementAccessExpression: + bind(node.argumentExpression); + break; + case SyntaxKind.CallExpression: + bindEach(node.typeArguments); + bindEach(node.arguments); + break; + } + if (postExpressionLabel) { + addAntecedent(postExpressionLabel, currentFlow); + addAntecedent(postExpressionLabel, finishFlowLabel(missingTarget)); + currentFlow = finishFlowLabel(postExpressionLabel); + } + } + + function bindAccessExpressionFlow(node: AccessExpression) { + if (isValidOptionalChain(node)) { + bindOptionalChainFlow(node); } else { bindEachChild(node); } + } + + function bindCallExpressionFlow(node: CallExpression) { + if (isValidOptionalChain(node)) { + bindOptionalChainFlow(node); + } + else { + // If the target of the call expression is a function expression or arrow function we have + // an immediately invoked function expression (IIFE). Initialize the flowNode property to + // the current control flow (which includes evaluation of the IIFE arguments). + const expr = skipParentheses(node.expression); + if (expr.kind === SyntaxKind.FunctionExpression || expr.kind === SyntaxKind.ArrowFunction) { + bindEach(node.typeArguments); + bindEach(node.arguments); + bind(node.expression); + } + else { + bindEachChild(node); + } + } if (node.expression.kind === SyntaxKind.PropertyAccessExpression) { const propertyAccess = node.expression; if (isNarrowableOperand(propertyAccess.expression) && isPushOrUnshiftIdentifier(propertyAccess.name)) { diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 8b717fa5778dc..05fd3b445a47c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -333,6 +333,10 @@ 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 @@ -15158,6 +15162,12 @@ namespace ts { 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, @@ -15172,7 +15182,7 @@ namespace ts { // 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 { isOptional, type: isOptional ? getNonNullableType(type) : 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 @@ -15185,7 +15195,7 @@ namespace ts { } type = checkNonNullType(type, expression, nullDiagnostic, undefinedDiagnostic, nullOrUndefinedDiagnostic); - return { isOptional, type }; + return createPooledOptionalTypeResult(isOptional, type); } /** @@ -17205,7 +17215,7 @@ namespace ts { // 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) : - node.expression.kind !== SyntaxKind.SuperKeyword ? checkNonNullExpression(node.expression) : + node.expression.kind !== SyntaxKind.SuperKeyword ? checkOptionalExpression(node, node.expression).type : undefined; const signatures = getSignaturesOfType(funcType && getApparentType(funcType) || unknownType, SignatureKind.Call); const candidate = signatures.length === 1 && !signatures[0].typeParameters ? signatures[0] : @@ -17377,6 +17387,9 @@ namespace ts { else if (flags & FlowFlags.Condition) { type = getTypeAtFlowCondition(flow); } + else if (flags & FlowFlags.OptionalChain) { + type = getTypeAtFlowOptionalChain(flow); + } else if (flags & FlowFlags.SwitchClause) { type = getTypeAtSwitchClause(flow); } @@ -17583,6 +17596,22 @@ namespace ts { return createFlowType(resultType, incomplete); } + function getTypeAtFlowOptionalChain(flow: FlowOptionalChain): FlowType { + const flowType = getTypeAtFlowNode(flow.antecedent); + const type = getTypeFromFlowType(flowType); + if (type.flags & TypeFlags.Never) { + return flowType; + } + + const assumePresent = (flow.flags & FlowFlags.Present) !== 0; + const nonEvolvingType = finalizeEvolvingArrayType(type); + const narrowedType = narrowTypeByOptionality(nonEvolvingType, flow.node, assumePresent); + if (narrowedType === nonEvolvingType) { + return flowType; + } + return createFlowType(narrowedType, isIncomplete(flowType)); + } + function getTypeAtSwitchClause(flow: FlowSwitchClause): FlowType { const expr = flow.switchStatement.expression; const flowType = getTypeAtFlowNode(flow.antecedent); @@ -18123,7 +18152,7 @@ namespace ts { function narrowTypeByCallExpression(type: Type, callExpression: CallExpression, assumeTrue: boolean): Type { if (hasMatchingArgument(callExpression, reference)) { - const signature = getEffectsSignature(callExpression); + const signature = assumeTrue || !isCallChain(callExpression) ? getEffectsSignature(callExpression) : undefined; const predicate = signature && getTypePredicateOfSignature(signature); if (predicate && (predicate.kind === TypePredicateKind.This || predicate.kind === TypePredicateKind.Identifier)) { return narrowTypeByTypePredicate(type, predicate, callExpression, assumeTrue); @@ -18187,6 +18216,19 @@ namespace ts { } return type; } + + function narrowTypeByOptionality(type: Type, expr: Expression, assumePresent: boolean): Type { + if (isMatchingReference(reference, expr)) { + return getTypeWithFacts(type, assumePresent ? TypeFacts.NEUndefinedOrNull : TypeFacts.EQUndefinedOrNull); + } + if (isMatchingReferenceDiscriminant(expr, declaredType)) { + return narrowTypeByDiscriminant(type, expr, t => getTypeWithFacts(t, assumePresent ? TypeFacts.NEUndefinedOrNull : TypeFacts.EQUndefinedOrNull)); + } + if (containsMatchingReferenceDiscriminant(reference, expr)) { + return declaredType; + } + return type; + } } function getTypeOfSymbolAtLocation(symbol: Symbol, location: Node) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 78b9191672d4f..0b2571628145d 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1801,6 +1801,22 @@ namespace ts { _optionalChainBrand: any; } + /* @internal */ + export interface PropertyAccessChainRoot extends PropertyAccessChain { + questionDotToken: QuestionDotToken; + } + + /* @internal */ + export interface ValidPropertyAccessChainLink extends PropertyAccessChain { + questionDotToken: undefined; + expression: ValidOptionalChain; + } + + /* @internal */ + export type ValidPropertyAccessChain = + | PropertyAccessChainRoot + | ValidPropertyAccessChainLink; + export interface SuperPropertyAccessExpression extends PropertyAccessExpression { expression: SuperExpression; } @@ -1822,6 +1838,22 @@ namespace ts { _optionalChainBrand: any; } + /* @internal */ + export interface ElementAccessChainRoot extends ElementAccessChain { + questionDotToken: QuestionDotToken; + } + + /* @internal */ + export interface ValidElementAccessChainLink extends ElementAccessChain { + questionDotToken: undefined; + expression: ValidOptionalChain; + } + + /* @internal */ + export type ValidElementAccessChain = + | ElementAccessChainRoot + | ValidElementAccessChainLink; + export interface SuperElementAccessExpression extends ElementAccessExpression { expression: SuperExpression; } @@ -1841,12 +1873,42 @@ namespace ts { _optionalChainBrand: any; } + /* @internal */ + export interface CallChainRoot extends CallChain { + questionDotToken: QuestionDotToken; + } + + /* @internal */ + export interface ValidCallChainLink extends CallChain { + questionDotToken: undefined; + expression: ValidOptionalChain; + } + + /* @internal */ + export type ValidCallChain = + | CallChainRoot + | ValidCallChainLink; + export type OptionalChain = | PropertyAccessChain | ElementAccessChain | CallChain ; + /* @internal */ + export type OptionalChainRoot = + | PropertyAccessChainRoot + | ElementAccessChainRoot + | CallChainRoot + ; + + /* @internal */ + export type ValidOptionalChain = + | ValidPropertyAccessChain + | ValidElementAccessChain + | ValidCallChain + ; + /** @internal */ export type BindableObjectDefinePropertyCall = CallExpression & { arguments: { 0: EntityNameExpression, 1: StringLiteralLike | NumericLiteral, 2: ObjectLiteralExpression } }; @@ -2620,10 +2682,14 @@ namespace ts { Shared = 1 << 11, // Referenced as antecedent more than once PreFinally = 1 << 12, // Injected edge that links pre-finally label and pre-try flow AfterFinally = 1 << 13, // Injected edge that links post-finally flow with the rest of the graph + Present = 1 << 14, // Optional Chain known to be neither null nor undefined. + Missing = 1 << 15, // Optional Chain known to be either null or undefined. /** @internal */ - Cached = 1 << 14, // Indicates that at least one cross-call cache entry exists for this node, even if not a loop participant + Cached = 1 << 16, // Indicates that at least one cross-call cache entry exists for this node, even if not a loop participant + Label = BranchLabel | LoopLabel, - Condition = TrueCondition | FalseCondition + Condition = TrueCondition | FalseCondition, + OptionalChain = Present | Missing, } export type FlowNode = @@ -2634,6 +2700,7 @@ namespace ts { | FlowAssignment | FlowCall | FlowCondition + | FlowOptionalChain | FlowSwitchClause | FlowArrayMutation; @@ -2686,6 +2753,11 @@ namespace ts { antecedent: FlowNode; } + export interface FlowOptionalChain extends FlowNodeBase { + node: Expression; + antecedent: FlowNode; + } + export interface FlowSwitchClause extends FlowNodeBase { switchStatement: SwitchStatement; clauseStart: number; // Start index of case/default clause range diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index f27c72c1192cb..1e729a24d6438 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -7087,6 +7087,24 @@ namespace ts { return node.kind === SyntaxKind.GetAccessor; } + /** + * Tests whether an OptionalChain is valid. We can parse an invalid optional chain if it contains an + * invalid TaggedTemplateChain per the ECMAScript syntax. + */ + /* @internal */ + export function isValidOptionalChain(node: Expression): node is ValidOptionalChain { + while (isOptionalChain(node)) { + if (node.questionDotToken) return true; + node = node.expression; + } + return false; + } + + /* @internal */ + export function isOptionalChainRoot(node: ValidOptionalChain): node is OptionalChainRoot { + return !!node.questionDotToken; + } + /** True if has jsdoc nodes attached to it. */ /* @internal */ // TODO: GH#19856 Would like to return `node is Node & { jsDoc: JSDoc[] }` but it causes long compile times diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index cc50762a3daf4..4880246dfef45 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -1697,10 +1697,13 @@ declare namespace ts { Shared = 2048, PreFinally = 4096, AfterFinally = 8192, + Present = 16384, + Missing = 32768, Label = 12, - Condition = 96 + Condition = 96, + OptionalChain = 49152 } - export type FlowNode = AfterFinallyFlow | PreFinallyFlow | FlowStart | FlowLabel | FlowAssignment | FlowCall | FlowCondition | FlowSwitchClause | FlowArrayMutation; + export type FlowNode = AfterFinallyFlow | PreFinallyFlow | FlowStart | FlowLabel | FlowAssignment | FlowCall | FlowCondition | FlowOptionalChain | FlowSwitchClause | FlowArrayMutation; export interface FlowNodeBase { flags: FlowFlags; id?: number; @@ -1733,6 +1736,10 @@ declare namespace ts { node: Expression; antecedent: FlowNode; } + export interface FlowOptionalChain extends FlowNodeBase { + node: Expression; + antecedent: FlowNode; + } export interface FlowSwitchClause extends FlowNodeBase { switchStatement: SwitchStatement; clauseStart: number; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 0a9b386b1517e..81487c1cd5db7 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -1697,10 +1697,13 @@ declare namespace ts { Shared = 2048, PreFinally = 4096, AfterFinally = 8192, + Present = 16384, + Missing = 32768, Label = 12, - Condition = 96 + Condition = 96, + OptionalChain = 49152 } - export type FlowNode = AfterFinallyFlow | PreFinallyFlow | FlowStart | FlowLabel | FlowAssignment | FlowCall | FlowCondition | FlowSwitchClause | FlowArrayMutation; + export type FlowNode = AfterFinallyFlow | PreFinallyFlow | FlowStart | FlowLabel | FlowAssignment | FlowCall | FlowCondition | FlowOptionalChain | FlowSwitchClause | FlowArrayMutation; export interface FlowNodeBase { flags: FlowFlags; id?: number; @@ -1733,6 +1736,10 @@ declare namespace ts { node: Expression; antecedent: FlowNode; } + export interface FlowOptionalChain extends FlowNodeBase { + node: Expression; + antecedent: FlowNode; + } export interface FlowSwitchClause extends FlowNodeBase { switchStatement: SwitchStatement; clauseStart: number; diff --git a/tests/baselines/reference/controlFlowOptionalChain.errors.txt b/tests/baselines/reference/controlFlowOptionalChain.errors.txt new file mode 100644 index 0000000000000..82f5af3cf63c1 --- /dev/null +++ b/tests/baselines/reference/controlFlowOptionalChain.errors.txt @@ -0,0 +1,125 @@ +tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(10,1): error TS2454: Variable 'a' is used before being assigned. +tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(14,1): error TS2454: Variable 'b' is used before being assigned. +tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(18,1): error TS2454: Variable 'c' is used before being assigned. +tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(22,1): error TS2454: Variable 'd' is used before being assigned. +tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(62,5): error TS2532: Object is possibly 'undefined'. + + +==== tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts (5 errors) ==== + // assignments in shortcutting chain + declare const o: undefined | { + [key: string]: any; + [key: number]: any; + (...args: any[]): any; + }; + + let a: number; + o?.[a = 1]; + a.toString(); + ~ +!!! error TS2454: Variable 'a' is used before being assigned. + + let b: number; + o?.x[b = 1]; + b.toString(); + ~ +!!! error TS2454: Variable 'b' is used before being assigned. + + let c: number; + o?.(c = 1) + c.toString(); + ~ +!!! error TS2454: Variable 'c' is used before being assigned. + + let d: number; + o?.x(d = 1); + d.toString(); + ~ +!!! error TS2454: Variable 'd' is used before being assigned. + + // type predicates + declare const f: undefined | ((x: any) => x is number); + declare const x: string | number; + if (f?.(x)) { + x; // number + f; // TODO: still possibly undefined, should be defined + } + else { + x; // string | number + f; // still possibly undefined + } + x; + f; + + declare const o2: { f(x: any): x is number; } | undefined; + if (o2?.f(x)) { + x; // number + o2; // TODO: still possibly undefined, should be defined + } + else { + x; // string | number + o2; // still possibly undefined. + } + x; + o2; + + declare const o3: { x: 1, y: string } | { x: 2, y: number } | undefined; + if (o3?.x === 1) { + o3; // TODO: still possibly undefined, should be defined + } + else { + o3; + } + o3; + + declare const o4: { x?: { y: boolean } }; + if (o4.x?.y) { + o4.x; // TODO: still possibly undefined, should be defined + o4.x.y; + ~~~~ +!!! error TS2532: Object is possibly 'undefined'. + } + + interface Base { + f(): this is Derived; + } + + interface Derived extends Base { + x: number; + } + + declare const o5: Base | undefined; + if (o5?.f()) { + o5; // Derived + } + else { + o5; // Base | undefined + } + o5; // Base | undefined + + // asserts + declare const isDefined: (value: T) => asserts value is NonNullable; + declare const isString: (value: unknown) => asserts value is string; + declare const maybeIsString: undefined | ((value: unknown) => asserts value is string); + declare const maybeNever: undefined | (() => never); + + function f01(x: unknown) { + if (!!true) { + isString?.(x); + x; // string + } + if (!!true) { + maybeIsString?.(x); + x; // unknown + } + if (!!true) { + isDefined(maybeIsString); + maybeIsString?.(x); + x; // TODO: is unknown, should be string + } + if (!!true) { + maybeNever?.(); + x; // unknown + } + } + \ No newline at end of file diff --git a/tests/baselines/reference/controlFlowOptionalChain.js b/tests/baselines/reference/controlFlowOptionalChain.js new file mode 100644 index 0000000000000..26f5f1bae4dc2 --- /dev/null +++ b/tests/baselines/reference/controlFlowOptionalChain.js @@ -0,0 +1,182 @@ +//// [controlFlowOptionalChain.ts] +// assignments in shortcutting chain +declare const o: undefined | { + [key: string]: any; + [key: number]: any; + (...args: any[]): any; +}; + +let a: number; +o?.[a = 1]; +a.toString(); + +let b: number; +o?.x[b = 1]; +b.toString(); + +let c: number; +o?.(c = 1) +c.toString(); + +let d: number; +o?.x(d = 1); +d.toString(); + +// type predicates +declare const f: undefined | ((x: any) => x is number); +declare const x: string | number; +if (f?.(x)) { + x; // number + f; // TODO: still possibly undefined, should be defined +} +else { + x; // string | number + f; // still possibly undefined +} +x; +f; + +declare const o2: { f(x: any): x is number; } | undefined; +if (o2?.f(x)) { + x; // number + o2; // TODO: still possibly undefined, should be defined +} +else { + x; // string | number + o2; // still possibly undefined. +} +x; +o2; + +declare const o3: { x: 1, y: string } | { x: 2, y: number } | undefined; +if (o3?.x === 1) { + o3; // TODO: still possibly undefined, should be defined +} +else { + o3; +} +o3; + +declare const o4: { x?: { y: boolean } }; +if (o4.x?.y) { + o4.x; // TODO: still possibly undefined, should be defined + o4.x.y; +} + +interface Base { + f(): this is Derived; +} + +interface Derived extends Base { + x: number; +} + +declare const o5: Base | undefined; +if (o5?.f()) { + o5; // Derived +} +else { + o5; // Base | undefined +} +o5; // Base | undefined + +// asserts +declare const isDefined: (value: T) => asserts value is NonNullable; +declare const isString: (value: unknown) => asserts value is string; +declare const maybeIsString: undefined | ((value: unknown) => asserts value is string); +declare const maybeNever: undefined | (() => never); + +function f01(x: unknown) { + if (!!true) { + isString?.(x); + x; // string + } + if (!!true) { + maybeIsString?.(x); + x; // unknown + } + if (!!true) { + isDefined(maybeIsString); + maybeIsString?.(x); + x; // TODO: is unknown, should be string + } + if (!!true) { + maybeNever?.(); + x; // unknown + } +} + + +//// [controlFlowOptionalChain.js] +"use strict"; +var _a, _b, _c, _d, _e, _f, _g, _h, _j; +var a; +(_a = o) === null || _a === void 0 ? void 0 : _a[a = 1]; +a.toString(); +var b; +(_b = o) === null || _b === void 0 ? void 0 : _b.x[b = 1]; +b.toString(); +var c; +(_c = o) === null || _c === void 0 ? void 0 : _c(c = 1); +c.toString(); +var d; +(_d = o) === null || _d === void 0 ? void 0 : _d.x(d = 1); +d.toString(); +if ((_e = f) === null || _e === void 0 ? void 0 : _e(x)) { + x; // number + f; // TODO: still possibly undefined, should be defined +} +else { + x; // string | number + f; // still possibly undefined +} +x; +f; +if ((_f = o2) === null || _f === void 0 ? void 0 : _f.f(x)) { + x; // number + o2; // TODO: still possibly undefined, should be defined +} +else { + x; // string | number + o2; // still possibly undefined. +} +x; +o2; +if (((_g = o3) === null || _g === void 0 ? void 0 : _g.x) === 1) { + o3; // TODO: still possibly undefined, should be defined +} +else { + o3; +} +o3; +if ((_h = o4.x) === null || _h === void 0 ? void 0 : _h.y) { + o4.x; // TODO: still possibly undefined, should be defined + o4.x.y; +} +if ((_j = o5) === null || _j === void 0 ? void 0 : _j.f()) { + o5; // Derived +} +else { + o5; // Base | undefined +} +o5; // Base | undefined +function f01(x) { + var _a, _b, _c, _d; + if (!!true) { + (_a = isString) === null || _a === void 0 ? void 0 : _a(x); + x; // string + } + if (!!true) { + (_b = maybeIsString) === null || _b === void 0 ? void 0 : _b(x); + x; // unknown + } + if (!!true) { + isDefined(maybeIsString); + (_c = maybeIsString) === null || _c === void 0 ? void 0 : _c(x); + x; // TODO: is unknown, should be string + } + if (!!true) { + (_d = maybeNever) === null || _d === void 0 ? void 0 : _d(); + x; // unknown + } +} diff --git a/tests/baselines/reference/controlFlowOptionalChain.symbols b/tests/baselines/reference/controlFlowOptionalChain.symbols new file mode 100644 index 0000000000000..7c02a33f766ff --- /dev/null +++ b/tests/baselines/reference/controlFlowOptionalChain.symbols @@ -0,0 +1,273 @@ +=== tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts === +// assignments in shortcutting chain +declare const o: undefined | { +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 1, 13)) + + [key: string]: any; +>key : Symbol(key, Decl(controlFlowOptionalChain.ts, 2, 5)) + + [key: number]: any; +>key : Symbol(key, Decl(controlFlowOptionalChain.ts, 3, 5)) + + (...args: any[]): any; +>args : Symbol(args, Decl(controlFlowOptionalChain.ts, 4, 5)) + +}; + +let a: number; +>a : Symbol(a, Decl(controlFlowOptionalChain.ts, 7, 3)) + +o?.[a = 1]; +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 1, 13)) +>a : Symbol(a, Decl(controlFlowOptionalChain.ts, 7, 3)) + +a.toString(); +>a.toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) +>a : Symbol(a, Decl(controlFlowOptionalChain.ts, 7, 3)) +>toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + +let b: number; +>b : Symbol(b, Decl(controlFlowOptionalChain.ts, 11, 3)) + +o?.x[b = 1]; +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 1, 13)) +>b : Symbol(b, Decl(controlFlowOptionalChain.ts, 11, 3)) + +b.toString(); +>b.toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) +>b : Symbol(b, Decl(controlFlowOptionalChain.ts, 11, 3)) +>toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + +let c: number; +>c : Symbol(c, Decl(controlFlowOptionalChain.ts, 15, 3)) + +o?.(c = 1) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 1, 13)) +>c : Symbol(c, Decl(controlFlowOptionalChain.ts, 15, 3)) + +c.toString(); +>c.toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) +>c : Symbol(c, Decl(controlFlowOptionalChain.ts, 15, 3)) +>toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + +let d: number; +>d : Symbol(d, Decl(controlFlowOptionalChain.ts, 19, 3)) + +o?.x(d = 1); +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 1, 13)) +>d : Symbol(d, Decl(controlFlowOptionalChain.ts, 19, 3)) + +d.toString(); +>d.toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) +>d : Symbol(d, Decl(controlFlowOptionalChain.ts, 19, 3)) +>toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + +// type predicates +declare const f: undefined | ((x: any) => x is number); +>f : Symbol(f, Decl(controlFlowOptionalChain.ts, 24, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 24, 31)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 24, 31)) + +declare const x: string | number; +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 25, 13)) + +if (f?.(x)) { +>f : Symbol(f, Decl(controlFlowOptionalChain.ts, 24, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 25, 13)) + + x; // number +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 25, 13)) + + f; // TODO: still possibly undefined, should be defined +>f : Symbol(f, Decl(controlFlowOptionalChain.ts, 24, 13)) +} +else { + x; // string | number +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 25, 13)) + + f; // still possibly undefined +>f : Symbol(f, Decl(controlFlowOptionalChain.ts, 24, 13)) +} +x; +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 25, 13)) + +f; +>f : Symbol(f, Decl(controlFlowOptionalChain.ts, 24, 13)) + +declare const o2: { f(x: any): x is number; } | undefined; +>o2 : Symbol(o2, Decl(controlFlowOptionalChain.ts, 37, 13)) +>f : Symbol(f, Decl(controlFlowOptionalChain.ts, 37, 19)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 37, 22)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 37, 22)) + +if (o2?.f(x)) { +>o2?.f : Symbol(f, Decl(controlFlowOptionalChain.ts, 37, 19)) +>o2 : Symbol(o2, Decl(controlFlowOptionalChain.ts, 37, 13)) +>f : Symbol(f, Decl(controlFlowOptionalChain.ts, 37, 19)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 25, 13)) + + x; // number +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 25, 13)) + + o2; // TODO: still possibly undefined, should be defined +>o2 : Symbol(o2, Decl(controlFlowOptionalChain.ts, 37, 13)) +} +else { + x; // string | number +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 25, 13)) + + o2; // still possibly undefined. +>o2 : Symbol(o2, Decl(controlFlowOptionalChain.ts, 37, 13)) +} +x; +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 25, 13)) + +o2; +>o2 : Symbol(o2, Decl(controlFlowOptionalChain.ts, 37, 13)) + +declare const o3: { x: 1, y: string } | { x: 2, y: number } | undefined; +>o3 : Symbol(o3, Decl(controlFlowOptionalChain.ts, 49, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 49, 19)) +>y : Symbol(y, Decl(controlFlowOptionalChain.ts, 49, 25)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 49, 41)) +>y : Symbol(y, Decl(controlFlowOptionalChain.ts, 49, 47)) + +if (o3?.x === 1) { +>o3?.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 49, 19), Decl(controlFlowOptionalChain.ts, 49, 41)) +>o3 : Symbol(o3, Decl(controlFlowOptionalChain.ts, 49, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 49, 19), Decl(controlFlowOptionalChain.ts, 49, 41)) + + o3; // TODO: still possibly undefined, should be defined +>o3 : Symbol(o3, Decl(controlFlowOptionalChain.ts, 49, 13)) +} +else { + o3; +>o3 : Symbol(o3, Decl(controlFlowOptionalChain.ts, 49, 13)) +} +o3; +>o3 : Symbol(o3, Decl(controlFlowOptionalChain.ts, 49, 13)) + +declare const o4: { x?: { y: boolean } }; +>o4 : Symbol(o4, Decl(controlFlowOptionalChain.ts, 58, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 58, 19)) +>y : Symbol(y, Decl(controlFlowOptionalChain.ts, 58, 25)) + +if (o4.x?.y) { +>o4.x?.y : Symbol(y, Decl(controlFlowOptionalChain.ts, 58, 25)) +>o4.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 58, 19)) +>o4 : Symbol(o4, Decl(controlFlowOptionalChain.ts, 58, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 58, 19)) +>y : Symbol(y, Decl(controlFlowOptionalChain.ts, 58, 25)) + + o4.x; // TODO: still possibly undefined, should be defined +>o4.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 58, 19)) +>o4 : Symbol(o4, Decl(controlFlowOptionalChain.ts, 58, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 58, 19)) + + o4.x.y; +>o4.x.y : Symbol(y, Decl(controlFlowOptionalChain.ts, 58, 25)) +>o4.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 58, 19)) +>o4 : Symbol(o4, Decl(controlFlowOptionalChain.ts, 58, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 58, 19)) +>y : Symbol(y, Decl(controlFlowOptionalChain.ts, 58, 25)) +} + +interface Base { +>Base : Symbol(Base, Decl(controlFlowOptionalChain.ts, 62, 1)) + + f(): this is Derived; +>f : Symbol(Base.f, Decl(controlFlowOptionalChain.ts, 64, 16)) +>Derived : Symbol(Derived, Decl(controlFlowOptionalChain.ts, 66, 1)) +} + +interface Derived extends Base { +>Derived : Symbol(Derived, Decl(controlFlowOptionalChain.ts, 66, 1)) +>Base : Symbol(Base, Decl(controlFlowOptionalChain.ts, 62, 1)) + + x: number; +>x : Symbol(Derived.x, Decl(controlFlowOptionalChain.ts, 68, 32)) +} + +declare const o5: Base | undefined; +>o5 : Symbol(o5, Decl(controlFlowOptionalChain.ts, 72, 13)) +>Base : Symbol(Base, Decl(controlFlowOptionalChain.ts, 62, 1)) + +if (o5?.f()) { +>o5?.f : Symbol(Base.f, Decl(controlFlowOptionalChain.ts, 64, 16)) +>o5 : Symbol(o5, Decl(controlFlowOptionalChain.ts, 72, 13)) +>f : Symbol(Base.f, Decl(controlFlowOptionalChain.ts, 64, 16)) + + o5; // Derived +>o5 : Symbol(o5, Decl(controlFlowOptionalChain.ts, 72, 13)) +} +else { + o5; // Base | undefined +>o5 : Symbol(o5, Decl(controlFlowOptionalChain.ts, 72, 13)) +} +o5; // Base | undefined +>o5 : Symbol(o5, Decl(controlFlowOptionalChain.ts, 72, 13)) + +// asserts +declare const isDefined: (value: T) => asserts value is NonNullable; +>isDefined : Symbol(isDefined, Decl(controlFlowOptionalChain.ts, 82, 13)) +>T : Symbol(T, Decl(controlFlowOptionalChain.ts, 82, 26)) +>value : Symbol(value, Decl(controlFlowOptionalChain.ts, 82, 29)) +>T : Symbol(T, Decl(controlFlowOptionalChain.ts, 82, 26)) +>value : Symbol(value, Decl(controlFlowOptionalChain.ts, 82, 29)) +>NonNullable : Symbol(NonNullable, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(controlFlowOptionalChain.ts, 82, 26)) + +declare const isString: (value: unknown) => asserts value is string; +>isString : Symbol(isString, Decl(controlFlowOptionalChain.ts, 83, 13)) +>value : Symbol(value, Decl(controlFlowOptionalChain.ts, 83, 25)) +>value : Symbol(value, Decl(controlFlowOptionalChain.ts, 83, 25)) + +declare const maybeIsString: undefined | ((value: unknown) => asserts value is string); +>maybeIsString : Symbol(maybeIsString, Decl(controlFlowOptionalChain.ts, 84, 13)) +>value : Symbol(value, Decl(controlFlowOptionalChain.ts, 84, 43)) +>value : Symbol(value, Decl(controlFlowOptionalChain.ts, 84, 43)) + +declare const maybeNever: undefined | (() => never); +>maybeNever : Symbol(maybeNever, Decl(controlFlowOptionalChain.ts, 85, 13)) + +function f01(x: unknown) { +>f01 : Symbol(f01, Decl(controlFlowOptionalChain.ts, 85, 52)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 87, 13)) + + if (!!true) { + isString?.(x); +>isString : Symbol(isString, Decl(controlFlowOptionalChain.ts, 83, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 87, 13)) + + x; // string +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 87, 13)) + } + if (!!true) { + maybeIsString?.(x); +>maybeIsString : Symbol(maybeIsString, Decl(controlFlowOptionalChain.ts, 84, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 87, 13)) + + x; // unknown +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 87, 13)) + } + if (!!true) { + isDefined(maybeIsString); +>isDefined : Symbol(isDefined, Decl(controlFlowOptionalChain.ts, 82, 13)) +>maybeIsString : Symbol(maybeIsString, Decl(controlFlowOptionalChain.ts, 84, 13)) + + maybeIsString?.(x); +>maybeIsString : Symbol(maybeIsString, Decl(controlFlowOptionalChain.ts, 84, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 87, 13)) + + x; // TODO: is unknown, should be string +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 87, 13)) + } + if (!!true) { + maybeNever?.(); +>maybeNever : Symbol(maybeNever, Decl(controlFlowOptionalChain.ts, 85, 13)) + + x; // unknown +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 87, 13)) + } +} + diff --git a/tests/baselines/reference/controlFlowOptionalChain.types b/tests/baselines/reference/controlFlowOptionalChain.types new file mode 100644 index 0000000000000..046bc5272e06a --- /dev/null +++ b/tests/baselines/reference/controlFlowOptionalChain.types @@ -0,0 +1,303 @@ +=== tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts === +// assignments in shortcutting chain +declare const o: undefined | { +>o : { (...args: any[]): any; [key: string]: any; [key: number]: any; } | undefined + + [key: string]: any; +>key : string + + [key: number]: any; +>key : number + + (...args: any[]): any; +>args : any[] + +}; + +let a: number; +>a : number + +o?.[a = 1]; +>o?.[a = 1] : any +>o : { (...args: any[]): any; [key: string]: any; [key: number]: any; } | undefined +>a = 1 : 1 +>a : number +>1 : 1 + +a.toString(); +>a.toString() : string +>a.toString : (radix?: number | undefined) => string +>a : number +>toString : (radix?: number | undefined) => string + +let b: number; +>b : number + +o?.x[b = 1]; +>o?.x[b = 1] : any +>o?.x : any +>o : { (...args: any[]): any; [key: string]: any; [key: number]: any; } | undefined +>x : any +>b = 1 : 1 +>b : number +>1 : 1 + +b.toString(); +>b.toString() : string +>b.toString : (radix?: number | undefined) => string +>b : number +>toString : (radix?: number | undefined) => string + +let c: number; +>c : number + +o?.(c = 1) +>o?.(c = 1) : any +>o : { (...args: any[]): any; [key: string]: any; [key: number]: any; } | undefined +>c = 1 : 1 +>c : number +>1 : 1 + +c.toString(); +>c.toString() : string +>c.toString : (radix?: number | undefined) => string +>c : number +>toString : (radix?: number | undefined) => string + +let d: number; +>d : number + +o?.x(d = 1); +>o?.x(d = 1) : any +>o?.x : any +>o : { (...args: any[]): any; [key: string]: any; [key: number]: any; } | undefined +>x : any +>d = 1 : 1 +>d : number +>1 : 1 + +d.toString(); +>d.toString() : string +>d.toString : (radix?: number | undefined) => string +>d : number +>toString : (radix?: number | undefined) => string + +// type predicates +declare const f: undefined | ((x: any) => x is number); +>f : ((x: any) => x is number) | undefined +>x : any + +declare const x: string | number; +>x : string | number + +if (f?.(x)) { +>f?.(x) : boolean | undefined +>f : ((x: any) => x is number) | undefined +>x : string | number + + x; // number +>x : number + + f; // TODO: still possibly undefined, should be defined +>f : ((x: any) => x is number) | undefined +} +else { + x; // string | number +>x : string | number + + f; // still possibly undefined +>f : ((x: any) => x is number) | undefined +} +x; +>x : string | number + +f; +>f : ((x: any) => x is number) | undefined + +declare const o2: { f(x: any): x is number; } | undefined; +>o2 : { f(x: any): x is number; } | undefined +>f : (x: any) => x is number +>x : any + +if (o2?.f(x)) { +>o2?.f(x) : boolean | undefined +>o2?.f : ((x: any) => x is number) | undefined +>o2 : { f(x: any): x is number; } | undefined +>f : ((x: any) => x is number) | undefined +>x : string | number + + x; // number +>x : number + + o2; // TODO: still possibly undefined, should be defined +>o2 : { f(x: any): x is number; } | undefined +} +else { + x; // string | number +>x : string | number + + o2; // still possibly undefined. +>o2 : { f(x: any): x is number; } | undefined +} +x; +>x : string | number + +o2; +>o2 : { f(x: any): x is number; } | undefined + +declare const o3: { x: 1, y: string } | { x: 2, y: number } | undefined; +>o3 : { x: 1; y: string; } | { x: 2; y: number; } | undefined +>x : 1 +>y : string +>x : 2 +>y : number + +if (o3?.x === 1) { +>o3?.x === 1 : boolean +>o3?.x : 1 | 2 | undefined +>o3 : { x: 1; y: string; } | { x: 2; y: number; } | undefined +>x : 1 | 2 | undefined +>1 : 1 + + o3; // TODO: still possibly undefined, should be defined +>o3 : { x: 1; y: string; } | { x: 2; y: number; } | undefined +} +else { + o3; +>o3 : { x: 1; y: string; } | { x: 2; y: number; } | undefined +} +o3; +>o3 : { x: 1; y: string; } | { x: 2; y: number; } | undefined + +declare const o4: { x?: { y: boolean } }; +>o4 : { x?: { y: boolean; } | undefined; } +>x : { y: boolean; } | undefined +>y : boolean + +if (o4.x?.y) { +>o4.x?.y : boolean | undefined +>o4.x : { y: boolean; } | undefined +>o4 : { x?: { y: boolean; } | undefined; } +>x : { y: boolean; } | undefined +>y : boolean | undefined + + o4.x; // TODO: still possibly undefined, should be defined +>o4.x : { y: boolean; } | undefined +>o4 : { x?: { y: boolean; } | undefined; } +>x : { y: boolean; } | undefined + + o4.x.y; +>o4.x.y : true +>o4.x : { y: boolean; } | undefined +>o4 : { x?: { y: boolean; } | undefined; } +>x : { y: boolean; } | undefined +>y : true +} + +interface Base { + f(): this is Derived; +>f : () => this is Derived +} + +interface Derived extends Base { + x: number; +>x : number +} + +declare const o5: Base | undefined; +>o5 : Base | undefined + +if (o5?.f()) { +>o5?.f() : boolean | undefined +>o5?.f : (() => this is Derived) | undefined +>o5 : Base | undefined +>f : (() => this is Derived) | undefined + + o5; // Derived +>o5 : Derived +} +else { + o5; // Base | undefined +>o5 : Base | undefined +} +o5; // Base | undefined +>o5 : Base | undefined + +// asserts +declare const isDefined: (value: T) => asserts value is NonNullable; +>isDefined : (value: T) => asserts value is NonNullable +>value : T + +declare const isString: (value: unknown) => asserts value is string; +>isString : (value: unknown) => asserts value is string +>value : unknown + +declare const maybeIsString: undefined | ((value: unknown) => asserts value is string); +>maybeIsString : ((value: unknown) => asserts value is string) | undefined +>value : unknown + +declare const maybeNever: undefined | (() => never); +>maybeNever : (() => never) | undefined + +function f01(x: unknown) { +>f01 : (x: unknown) => void +>x : unknown + + if (!!true) { +>!!true : true +>!true : false +>true : true + + isString?.(x); +>isString?.(x) : void +>isString : (value: unknown) => asserts value is string +>x : unknown + + x; // string +>x : string + } + if (!!true) { +>!!true : true +>!true : false +>true : true + + maybeIsString?.(x); +>maybeIsString?.(x) : void | undefined +>maybeIsString : ((value: unknown) => asserts value is string) | undefined +>x : unknown + + x; // unknown +>x : unknown + } + if (!!true) { +>!!true : true +>!true : false +>true : true + + isDefined(maybeIsString); +>isDefined(maybeIsString) : void +>isDefined : (value: T) => asserts value is NonNullable +>maybeIsString : ((value: unknown) => asserts value is string) | undefined + + maybeIsString?.(x); +>maybeIsString?.(x) : void +>maybeIsString : (value: unknown) => asserts value is string +>x : unknown + + x; // TODO: is unknown, should be string +>x : unknown + } + if (!!true) { +>!!true : true +>!true : false +>true : true + + maybeNever?.(); +>maybeNever?.() : undefined +>maybeNever : (() => never) | undefined + + x; // unknown +>x : unknown + } +} + diff --git a/tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts b/tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts new file mode 100644 index 0000000000000..ec7118a1c8db0 --- /dev/null +++ b/tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts @@ -0,0 +1,109 @@ +// @strict: true +// @allowUnreachableCode: false + +// assignments in shortcutting chain +declare const o: undefined | { + [key: string]: any; + [key: number]: any; + (...args: any[]): any; +}; + +let a: number; +o?.[a = 1]; +a.toString(); + +let b: number; +o?.x[b = 1]; +b.toString(); + +let c: number; +o?.(c = 1) +c.toString(); + +let d: number; +o?.x(d = 1); +d.toString(); + +// type predicates +declare const f: undefined | ((x: any) => x is number); +declare const x: string | number; +if (f?.(x)) { + x; // number + f; // TODO: still possibly undefined, should be defined +} +else { + x; // string | number + f; // still possibly undefined +} +x; +f; + +declare const o2: { f(x: any): x is number; } | undefined; +if (o2?.f(x)) { + x; // number + o2; // TODO: still possibly undefined, should be defined +} +else { + x; // string | number + o2; // still possibly undefined. +} +x; +o2; + +declare const o3: { x: 1, y: string } | { x: 2, y: number } | undefined; +if (o3?.x === 1) { + o3; // TODO: still possibly undefined, should be defined +} +else { + o3; +} +o3; + +declare const o4: { x?: { y: boolean } }; +if (o4.x?.y) { + o4.x; // TODO: still possibly undefined, should be defined + o4.x.y; +} + +interface Base { + f(): this is Derived; +} + +interface Derived extends Base { + x: number; +} + +declare const o5: Base | undefined; +if (o5?.f()) { + o5; // Derived +} +else { + o5; // Base | undefined +} +o5; // Base | undefined + +// asserts +declare const isDefined: (value: T) => asserts value is NonNullable; +declare const isString: (value: unknown) => asserts value is string; +declare const maybeIsString: undefined | ((value: unknown) => asserts value is string); +declare const maybeNever: undefined | (() => never); + +function f01(x: unknown) { + if (!!true) { + isString?.(x); + x; // string + } + if (!!true) { + maybeIsString?.(x); + x; // unknown + } + if (!!true) { + isDefined(maybeIsString); + maybeIsString?.(x); + x; // TODO: is unknown, should be string + } + if (!!true) { + maybeNever?.(); + x; // unknown + } +} From 1d7446f1576bdb82010a52519b8536abc4a6c1d0 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Thu, 26 Sep 2019 12:49:50 -0700 Subject: [PATCH 09/19] PR Feedback and more tests --- src/compiler/emitter.ts | 4 +-- src/compiler/scanner.ts | 7 ++--- src/compiler/types.ts | 4 +-- src/compiler/utilities.ts | 26 ++++++++----------- src/services/completions.ts | 18 ++++++------- src/services/textChanges.ts | 4 +-- src/services/utilities.ts | 4 +-- .../fourslash/completionAfterQuestionDot.ts | 20 ++++++++++++++ .../completionAutoInsertQuestionDot.ts | 20 ++++++++++++++ 9 files changed, 72 insertions(+), 35 deletions(-) create mode 100644 tests/cases/fourslash/completionAfterQuestionDot.ts create mode 100644 tests/cases/fourslash/completionAutoInsertQuestionDot.ts diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 5d4ffd3565801..27c14d1617164 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -2258,8 +2258,8 @@ namespace ts { const shouldEmitDotDot = token.kind !== SyntaxKind.QuestionDotToken && mayNeedDotDotForPropertyAccess(expression) && - !writer.hasPrecedingComment() && - !writer.hasPrecedingWhitespace(); + !writer.hasTrailingComment() && + !writer.hasTrailingWhitespace(); if (shouldEmitDotDot) { writePunctuation("."); diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index e3d6a3508168d..18c96f94127d2 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -1829,10 +1829,11 @@ namespace ts { pos++; return token = SyntaxKind.GreaterThanToken; case CharacterCodes.question: - if (text.charCodeAt(pos + 1) === CharacterCodes.dot && !isDigit(text.charCodeAt(pos + 2))) { - return pos += 2, token = SyntaxKind.QuestionDotToken; - } pos++; + if (text.charCodeAt(pos) === CharacterCodes.dot && !isDigit(text.charCodeAt(pos + 1))) { + pos++; + return token = SyntaxKind.QuestionDotToken; + } return token = SyntaxKind.QuestionToken; case CharacterCodes.openBracket: pos++; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 0b2571628145d..946d7a46d56b5 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -6073,8 +6073,8 @@ namespace ts { getColumn(): number; getIndent(): number; isAtStartOfLine(): boolean; - hasPrecedingComment(): boolean; - hasPrecedingWhitespace(): boolean; + hasTrailingComment(): boolean; + hasTrailingWhitespace(): boolean; getTextPosWithWriteLine?(): number; } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 1e729a24d6438..e991a0fae21f2 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -78,8 +78,8 @@ namespace ts { getColumn: () => 0, getIndent: () => 0, isAtStartOfLine: () => false, - hasPrecedingComment: () => false, - hasPrecedingWhitespace: () => !!str.length && isWhiteSpaceLike(str.charCodeAt(str.length - 1)), + hasTrailingComment: () => false, + hasTrailingWhitespace: () => !!str.length && isWhiteSpaceLike(str.charCodeAt(str.length - 1)), // Completely ignore indentation for string writers. And map newlines to // a single space. @@ -3289,7 +3289,7 @@ namespace ts { let lineStart: boolean; let lineCount: number; let linePos: number; - let hasPrecedingComment = false; + let hasTrailingComment = false; function updateLineCountAndPosFor(s: string) { const lineStartsOfS = computeLineStarts(s); @@ -3315,12 +3315,12 @@ namespace ts { } function write(s: string) { - if (s) hasPrecedingComment = false; + if (s) hasTrailingComment = false; writeText(s); } function writeComment(s: string) { - if (s) hasPrecedingComment = true; + if (s) hasTrailingComment = true; writeText(s); } @@ -3330,14 +3330,14 @@ namespace ts { lineStart = true; lineCount = 0; linePos = 0; - hasPrecedingComment = false; + hasTrailingComment = false; } function rawWrite(s: string) { if (s !== undefined) { output += s; updateLineCountAndPosFor(s); - hasPrecedingComment = false; + hasTrailingComment = false; } } @@ -3353,7 +3353,7 @@ namespace ts { lineCount++; linePos = output.length; lineStart = true; - hasPrecedingComment = false; + hasTrailingComment = false; } } @@ -3376,8 +3376,8 @@ namespace ts { getColumn: () => lineStart ? indent * getIndentSize() : output.length - linePos, getText: () => output, isAtStartOfLine: () => lineStart, - hasPrecedingComment: () => hasPrecedingComment, - hasPrecedingWhitespace: () => !!output.length && isWhiteSpaceLike(output.charCodeAt(output.length - 1)), + hasTrailingComment: () => hasTrailingComment, + hasTrailingWhitespace: () => !!output.length && isWhiteSpaceLike(output.charCodeAt(output.length - 1)), clear: reset, reportInaccessibleThisError: noop, reportPrivateInBaseOfClassExpression: noop, @@ -4755,11 +4755,7 @@ namespace ts { } export function getDotOrQuestionDotToken(node: PropertyAccessExpression) { - if (node.questionDotToken) { - return node.questionDotToken; - } - - return createNode(SyntaxKind.DotToken, node.expression.end, node.name.pos) as DotToken; + return node.questionDotToken || createNode(SyntaxKind.DotToken, node.expression.end, node.name.pos) as DotToken; } } diff --git a/src/services/completions.ts b/src/services/completions.ts index c82ef7dbd357d..ddf3acfcadb5b 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -925,10 +925,7 @@ namespace ts.Completions { const symbolToOriginInfoMap: SymbolOriginInfoMap = []; const symbolToSortTextMap: SymbolSortTextMap = []; - if (isRightOfDot) { - getTypeScriptMemberSymbols(); - } - else if (isRightOfQuestionDot) { + if (isRightOfDot || isRightOfQuestionDot) { getTypeScriptMemberSymbols(); } else if (isRightOfOpenTag) { @@ -1125,13 +1122,16 @@ namespace ts.Completions { } function addSymbolOriginInfo(symbol: Symbol) { - if (insertAwait && preferences.includeCompletionsWithInsertText && !symbolToOriginInfoMap[getSymbolId(symbol)]) { - symbolToOriginInfoMap[getSymbolId(symbol)] = { kind: getNullableSymbolOriginInfoKind(SymbolOriginInfoKind.Promise) }; - } - else if (insertQuestionDot) { - symbolToOriginInfoMap[getSymbolId(symbol)] = { kind: SymbolOriginInfoKind.Nullable }; + if (preferences.includeCompletionsWithInsertText) { + if (insertAwait && !symbolToOriginInfoMap[getSymbolId(symbol)]) { + symbolToOriginInfoMap[getSymbolId(symbol)] = { kind: getNullableSymbolOriginInfoKind(SymbolOriginInfoKind.Promise) }; + } + else if (insertQuestionDot) { + symbolToOriginInfoMap[getSymbolId(symbol)] = { kind: SymbolOriginInfoKind.Nullable }; + } } } + function getNullableSymbolOriginInfoKind(kind: SymbolOriginInfoKind) { return insertQuestionDot ? kind | SymbolOriginInfoKind.Nullable : kind; } diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index e7a8f4bc24360..ffeeca62be35d 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -1051,8 +1051,8 @@ namespace ts.textChanges { getColumn, getIndent, isAtStartOfLine, - hasPrecedingComment: () => writer.hasPrecedingComment(), - hasPrecedingWhitespace: () => writer.hasPrecedingWhitespace(), + hasTrailingComment: () => writer.hasTrailingComment(), + hasTrailingWhitespace: () => writer.hasTrailingWhitespace(), clear }; } diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 0903fd06bf925..953e1423036dc 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1506,8 +1506,8 @@ namespace ts { getColumn: () => 0, getLine: () => 0, isAtStartOfLine: () => false, - hasPrecedingWhitespace: () => false, - hasPrecedingComment: () => false, + hasTrailingWhitespace: () => false, + hasTrailingComment: () => false, rawWrite: notImplemented, getIndent: () => indent, increaseIndent: () => { indent++; }, diff --git a/tests/cases/fourslash/completionAfterQuestionDot.ts b/tests/cases/fourslash/completionAfterQuestionDot.ts new file mode 100644 index 0000000000000..7d31e775a185a --- /dev/null +++ b/tests/cases/fourslash/completionAfterQuestionDot.ts @@ -0,0 +1,20 @@ +/// +// @strict: true + +//// interface User { +//// address?: { +//// city: string; +//// "postal code": string; +//// } +//// }; +//// declare const user: User; +//// user.address[|?./**/|] + +verify.completions({ + marker: "", + exact: [ + { name: "city", text: "(property) city: string" }, + { name: "postal code", text: "(property) \"postal code\": string", insertText: "?.[\"postal code\"]", replacementSpan: test.ranges()[0] } + ], + preferences: { includeInsertTextCompletions: true }, +}); diff --git a/tests/cases/fourslash/completionAutoInsertQuestionDot.ts b/tests/cases/fourslash/completionAutoInsertQuestionDot.ts new file mode 100644 index 0000000000000..e1998007c76a6 --- /dev/null +++ b/tests/cases/fourslash/completionAutoInsertQuestionDot.ts @@ -0,0 +1,20 @@ +/// +// @strict: true + +//// interface User { +//// address?: { +//// city: string; +//// "postal code": string; +//// } +//// }; +//// declare const user: User; +//// user.address[|./**/|] + +verify.completions({ + marker: "", + exact: [ + { name: "city", text: "(property) city: string", insertText: "?.city", replacementSpan: test.ranges()[0] }, + { name: "postal code", text: "(property) \"postal code\": string", insertText: "?.[\"postal code\"]", replacementSpan: test.ranges()[0] } + ], + preferences: { includeInsertTextCompletions: true }, +}); From b282b628259b8eb917beece81738789296e6defe Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Thu, 26 Sep 2019 15:55:16 -0700 Subject: [PATCH 10/19] Update to control flow --- src/compiler/binder.ts | 37 +- src/compiler/debug.ts | 273 +++++++++ .../controlFlowOptionalChain.errors.txt | 141 ++++- .../reference/controlFlowOptionalChain.js | 186 +++++-- .../controlFlowOptionalChain.symbols | 525 ++++++++++++++---- .../reference/controlFlowOptionalChain.types | 395 +++++++++++-- .../controlFlow/controlFlowOptionalChain.ts | 92 ++- 7 files changed, 1437 insertions(+), 212 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 6c3d9113dd249..162368bfa89e8 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -879,6 +879,22 @@ namespace ts { } } + function findAntecedent(flowNode: FlowNode, flags: FlowFlags) { + if (flowNode.flags & flags) { + return flowNode; + } + if (flowNode.flags & FlowFlags.BranchLabel) { + const branch = flowNode as FlowLabel; + if (branch.antecedents) { + for (const antecedent of branch.antecedents) { + if (antecedent.flags & flags) { + return antecedent; + } + } + } + } + } + function createFlowCondition(flags: FlowFlags, antecedent: FlowNode, expression: Expression | undefined): FlowNode { if (antecedent.flags & FlowFlags.Unreachable) { return antecedent; @@ -890,6 +906,15 @@ namespace ts { expression.kind === SyntaxKind.FalseKeyword && flags & FlowFlags.TrueCondition) { return unreachableFlow; } + + if (flags & FlowFlags.TrueCondition) { + // If the antecedent is an optional chain, only the Present branch can exist on the `true` condition + const presentFlow = findAntecedent(currentFlow, FlowFlags.Present); + if (presentFlow) { + antecedent = presentFlow; + } + } + if (!isNarrowingExpression(expression)) { return antecedent; } @@ -901,6 +926,12 @@ namespace ts { if (antecedent.flags & FlowFlags.Unreachable) { return antecedent; } + if (flags & FlowFlags.Present) { + const presentFlow = findAntecedent(currentFlow, FlowFlags.Present); + if (presentFlow) { + antecedent = presentFlow; + } + } setFlowNodeReferenced(antecedent); return flowNodeCreated({ flags, antecedent, node: expression }); } @@ -1505,14 +1536,14 @@ namespace ts { bind(node); currentPresentTarget = savedPresentTarget; currentMissingTarget = savedMissingTarget; - if (!isValidOptionalChain(node)) { + if (!isValidOptionalChain(node) || isOutermostOptionalChain(node)) { addAntecedent(presentTarget, createFlowOptionalChain(FlowFlags.Present, currentFlow, node)); addAntecedent(missingTarget, createFlowOptionalChain(FlowFlags.Missing, currentFlow, node)); } } function isOutermostOptionalChain(node: ValidOptionalChain) { - return !isOptionalChain(node.parent) || node.parent.expression !== node; + return !isOptionalChain(node.parent) || !!node.parent.questionDotToken || node.parent.expression !== node; } function bindOptionalChainFlow(node: ValidOptionalChain) { @@ -1520,7 +1551,7 @@ namespace ts { const presentTarget = postExpressionLabel ? createBranchLabel() : Debug.assertDefined(currentPresentTarget); const missingTarget = postExpressionLabel ? createBranchLabel() : Debug.assertDefined(currentMissingTarget); bindOptionalExpression(node.expression, presentTarget, missingTarget); - if (!isValidOptionalChain(node.expression)) { + if (!isValidOptionalChain(node.expression) || node.questionDotToken) { currentFlow = finishFlowLabel(presentTarget); } bind(node.questionDotToken); diff --git a/src/compiler/debug.ts b/src/compiler/debug.ts index f8b2ec784cdba..bdcd3d0369ca6 100644 --- a/src/compiler/debug.ts +++ b/src/compiler/debug.ts @@ -268,5 +268,278 @@ namespace ts { isDebugInfoEnabled = true; } + + /** + * Prints a control-flow graph for debugging purposes. + */ + export function printControlFlowGraph(flowNode: FlowNode) { + console.log(formatControlFlowGraph(flowNode)); + } + + export function formatControlFlowGraph(flowNode: FlowNode) { + const hasAntecedentFlags = + FlowFlags.Assignment | + FlowFlags.Condition | + FlowFlags.SwitchClause | + FlowFlags.ArrayMutation | + FlowFlags.Call | + FlowFlags.PreFinally | + FlowFlags.AfterFinally | + FlowFlags.OptionalChain; + + type hasAntecedent = + | FlowAssignment + | FlowCondition + | FlowSwitchClause + | FlowArrayMutation + | FlowCall + | PreFinallyFlow + | AfterFinallyFlow + | FlowOptionalChain + ; + + const flowNodes: FlowNode[] = []; + const sharedNodes = createMap(); + const sharedIdMap = createMap(); + let nextSharedId = 1; + trackSharedNodesInGraph(flowNode); + + const deferredFlowNodes: FlowNode[] = []; + const writtenFlowNodes: FlowNode[] = []; + const writer = createTextWriter("\n"); + writeFlowNode(flowNode, /*isAntecedent*/ false); + + while (deferredFlowNodes.length) { + let hasWrittenHeader = false; + const deferred = deferredFlowNodes.splice(0, deferredFlowNodes.length); + for (const sharedNode of deferred) { + if (!hasWrittenHeader) { + writer.writeLine(); + writer.rawWrite("..."); + writer.writeLine(); + hasWrittenHeader = true; + } + if (pushIfUnique(writtenFlowNodes, sharedNode)) { + writeFlowNode(sharedNode, /*isAntecedent*/ false); + } + } + } + + return writer.getText(); + + function trackSharedNodesInGraph(flowNode: FlowNode) { + if (flowNode.flags & FlowFlags.Shared) { + const flowId = `${getFlowId(flowNode)}`; + const isShared = sharedNodes.get(flowId); + if (isShared === undefined) { + sharedNodes.set(flowId, false) + } + else if (isShared === false) { + sharedNodes.set(flowId, true); + } + } + if (flowNode.flags & FlowFlags.Label) { + forEach((flowNode as FlowLabel).antecedents, trackSharedNodesInGraph); + } + else if (flowNode.flags & hasAntecedentFlags) { + trackSharedNodesInGraph((flowNode as hasAntecedent).antecedent); + } + } + + function getText(node: Node) { + return getSourceTextOfNodeFromSourceFile(getSourceFileOfNode(node), node); + } + + function writeLines(writer: EmitTextWriter, text: string, prefix = ""): void { + const lines = text.split(/\r\n?|\n/g); + const indentation = guessIndentation(lines); + for (const lineText of lines) { + const line = indentation ? lineText.slice(indentation) : lineText; + if (line.length) { + writer.write(prefix + line); + writer.writeLine(); + } + } + } + + function writeNode(node: Node | undefined) { + if (!node) return; + writer.increaseIndent(); + writeLines(writer, getText(node), "> "); + writer.decreaseIndent(); + } + + function getFlowId(flowNode: FlowNode) { + let index = flowNodes.indexOf(flowNode); + if (index === -1) { + index = flowNodes.length; + flowNodes.push(flowNode); + } + return index; + } + + function getHeader(flags: FlowFlags) { + if (flags & FlowFlags.Start) return "Start"; + if (flags & FlowFlags.BranchLabel) return "Branch"; + if (flags & FlowFlags.LoopLabel) return "Loop"; + if (flags & FlowFlags.Assignment) return "Assignment"; + if (flags & FlowFlags.TrueCondition) return "True"; + if (flags & FlowFlags.FalseCondition) return "False"; + if (flags & FlowFlags.SwitchClause) return "SwitchClause"; + if (flags & FlowFlags.ArrayMutation) return "ArrayMutation"; + if (flags & FlowFlags.Call) return "Call"; + if (flags & FlowFlags.PreFinally) return "PreFinally"; + if (flags & FlowFlags.AfterFinally) return "AfterFinally"; + if (flags & FlowFlags.Present) return "Present"; + if (flags & FlowFlags.Missing) return "Missing"; + if (flags & FlowFlags.Unreachable) return "Unreachable"; + return Debug.fail(); + } + + function writeHeader(flowNode: FlowNode, isAntecedent: boolean) { + writer.write("- "); + const flowId = `${getFlowId(flowNode)}`; + if (sharedNodes.get(flowId)) { + let sharedId = sharedIdMap.get(flowId); + if (!sharedId) { + sharedIdMap.set(flowId, sharedId = nextSharedId); + nextSharedId++; + } + writer.write(`(#${sharedId}) `); + } + writer.write(getHeader(flowNode.flags)); + if (isAntecedent && sharedNodes.get(flowId)) { + writer.write("..."); + } + else { + const attributes: string[] = []; + if (flowNode.flags & FlowFlags.PreFinally) { + if ((flowNode as PreFinallyFlow).lock.locked) { + attributes.push("locked"); + } + } + else if (flowNode.flags & FlowFlags.AfterFinally) { + if ((flowNode as AfterFinallyFlow).locked) { + attributes.push("locked"); + } + } + if (!(flowNode.flags & FlowFlags.Referenced)) { + attributes.push("unreferenced") + } + if (flowNode.flags & FlowFlags.Cached) { + attributes.push("cached"); + } + if (some(attributes)) { + writer.write(`[${attributes.join(', ')}]`); + } + } + writer.writeLine(); + } + + function writeAntecedent(antecedent: FlowNode) { + writer.increaseIndent(); + writeFlowNode(antecedent, /*isAntecedent*/ true); + writer.decreaseIndent(); + } + + function writeAntecedents(antecedents: FlowNode[] | undefined) { + if (antecedents) { + writer.increaseIndent(); + for (const antecedent of antecedents) { + writeFlowNode(antecedent, /*isAntecedent*/ true); + } + writer.decreaseIndent(); + } + } + + function writeFlowNode(flowNode: FlowNode, isAntecedent: boolean) { + writeHeader(flowNode, isAntecedent); + const flowId = `${getFlowId(flowNode)}`; + if (sharedNodes.get(flowId) && isAntecedent) { + pushIfUnique(deferredFlowNodes, flowNode); + } + else if (flowNode.flags & FlowFlags.Start) { + writeStart(flowNode as FlowStart); + } + else if (flowNode.flags & FlowFlags.Label) { + writeLabel(flowNode as FlowLabel); + } + else if (flowNode.flags & FlowFlags.Assignment) { + writeAssignment(flowNode as FlowAssignment); + } + else if (flowNode.flags & FlowFlags.Condition) { + writeCondition(flowNode as FlowCondition); + } + else if (flowNode.flags & FlowFlags.SwitchClause) { + writeSwitchClause(flowNode as FlowSwitchClause); + } + else if (flowNode.flags & FlowFlags.ArrayMutation) { + writeArrayMutation(flowNode as FlowArrayMutation); + } + else if (flowNode.flags & FlowFlags.Call) { + writeCall(flowNode as FlowCall); + } + else if (flowNode.flags & FlowFlags.PreFinally) { + writePreFinally(flowNode as PreFinallyFlow); + } + else if (flowNode.flags & FlowFlags.AfterFinally) { + writeAfterFinally(flowNode as AfterFinallyFlow); + } + else if (flowNode.flags & FlowFlags.OptionalChain) { + writeOptionalChain(flowNode as FlowOptionalChain); + } + else { + Debug.assert(!!(flowNode.flags & FlowFlags.Unreachable)); + } + } + + function writeStart(flowNode: FlowStart) { + writeNode(flowNode.node); + } + + function writeLabel(flowNode: FlowLabel) { + writeAntecedents(flowNode.antecedents); + } + + function writeAssignment(flowNode: FlowAssignment) { + writeNode(flowNode.node); + writeAntecedent(flowNode.antecedent); + } + + function writeCondition(flowNode: FlowCondition) { + writeNode(flowNode.node); + writeAntecedent(flowNode.antecedent); + } + + function writeSwitchClause(flowNode: FlowSwitchClause) { + for (let i = flowNode.clauseStart; i < flowNode.clauseEnd; i++) { + writeNode(flowNode.switchStatement.caseBlock.clauses[i]); + } + writeAntecedent(flowNode.antecedent); + } + + function writeArrayMutation(flowNode: FlowArrayMutation) { + writeNode(flowNode.node); + writeAntecedent(flowNode.antecedent); + } + + function writeCall(flowNode: FlowCall) { + writeNode(flowNode.node); + writeAntecedent(flowNode.antecedent); + } + + function writePreFinally(flowNode: PreFinallyFlow) { + writeAntecedent(flowNode.antecedent); + } + + function writeAfterFinally(flowNode: AfterFinallyFlow) { + writeAntecedent(flowNode.antecedent); + } + + function writeOptionalChain(flowNode: FlowOptionalChain) { + writeNode(flowNode.node); + writeAntecedent(flowNode.antecedent); + } + } } } diff --git a/tests/baselines/reference/controlFlowOptionalChain.errors.txt b/tests/baselines/reference/controlFlowOptionalChain.errors.txt index 82f5af3cf63c1..2b7588429d465 100644 --- a/tests/baselines/reference/controlFlowOptionalChain.errors.txt +++ b/tests/baselines/reference/controlFlowOptionalChain.errors.txt @@ -2,10 +2,25 @@ tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(10,1): error TS2 tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(14,1): error TS2454: Variable 'b' is used before being assigned. tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(18,1): error TS2454: Variable 'c' is used before being assigned. tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(22,1): error TS2454: Variable 'd' is used before being assigned. -tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(62,5): error TS2532: Object is possibly 'undefined'. +tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(35,5): error TS2722: Cannot invoke an object which is possibly 'undefined'. +tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(39,1): error TS2722: Cannot invoke an object which is possibly 'undefined'. +tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(52,5): error TS2532: Object is possibly 'undefined'. +tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(57,1): error TS2532: Object is possibly 'undefined'. +tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(68,5): error TS2532: Object is possibly 'undefined'. +tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(72,1): error TS2532: Object is possibly 'undefined'. +tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(83,5): error TS2532: Object is possibly 'undefined'. +tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(87,1): error TS2532: Object is possibly 'undefined'. +tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(104,5): error TS2532: Object is possibly 'undefined'. +tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(105,5): error TS2532: Object is possibly 'undefined'. +tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(105,5): error TS2532: Object is possibly 'undefined'. +tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(111,1): error TS2532: Object is possibly 'undefined'. +tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(112,1): error TS2532: Object is possibly 'undefined'. +tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(112,1): error TS2532: Object is possibly 'undefined'. +tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(130,5): error TS2532: Object is possibly 'undefined'. +tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(134,1): error TS2532: Object is possibly 'undefined'. -==== tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts (5 errors) ==== +==== tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts (20 errors) ==== // assignments in shortcutting chain declare const o: undefined | { [key: string]: any; @@ -41,44 +56,119 @@ tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(62,5): error TS2 declare const f: undefined | ((x: any) => x is number); declare const x: string | number; if (f?.(x)) { - x; // number - f; // TODO: still possibly undefined, should be defined + x; + f; + f(x); } else { - x; // string | number - f; // still possibly undefined + x; + f; + f(x); + ~ +!!! error TS2722: Cannot invoke an object which is possibly 'undefined'. } x; f; + f(x); + ~ +!!! error TS2722: Cannot invoke an object which is possibly 'undefined'. declare const o2: { f(x: any): x is number; } | undefined; if (o2?.f(x)) { - x; // number - o2; // TODO: still possibly undefined, should be defined + x; + o2.f; + o2?.f; + o2?.f(x); } else { - x; // string | number - o2; // still possibly undefined. + x; + o2; + o2?.f; + o2.f; + ~~ +!!! error TS2532: Object is possibly 'undefined'. } x; o2; + o2?.f; + o2.f; + ~~ +!!! error TS2532: Object is possibly 'undefined'. declare const o3: { x: 1, y: string } | { x: 2, y: number } | undefined; if (o3?.x === 1) { - o3; // TODO: still possibly undefined, should be defined + o3; + o3.x; + o3?.x; } else { o3; + o3?.x; + o3.x; + ~~ +!!! error TS2532: Object is possibly 'undefined'. } o3; + o3?.x; + o3.x; + ~~ +!!! error TS2532: Object is possibly 'undefined'. declare const o4: { x?: { y: boolean } }; if (o4.x?.y) { - o4.x; // TODO: still possibly undefined, should be defined + o4.x; + o4.x?.y; o4.x.y; + } + else { + o4.x; + o4.x?.y; + o4.x.y; + ~~~~ +!!! error TS2532: Object is possibly 'undefined'. + } + o4.x; + o4.x?.y; + o4.x.y; + ~~~~ +!!! error TS2532: Object is possibly 'undefined'. + + declare const o5: { x?: { y: { z?: { w: boolean } } } }; + if (o5.x?.y.z?.w) { + o5.x; + o5.x.y; + o5.x.y.z; + o5.x.y.z.w; + o5.x.y.z?.w; + o5.x?.y.z.w; + o5.x?.y.z?.w; + } + else { + o5.x; + o5.x?.y; + o5.x?.y.z; + o5.x?.y.z?.w; + o5.x.y; + ~~~~ +!!! error TS2532: Object is possibly 'undefined'. + o5.x.y.z.w; ~~~~ +!!! error TS2532: Object is possibly 'undefined'. + ~~~~~~~~ !!! error TS2532: Object is possibly 'undefined'. } + o5.x; + o5.x?.y; + o5.x?.y.z; + o5.x?.y.z?.w; + o5.x.y; + ~~~~ +!!! error TS2532: Object is possibly 'undefined'. + o5.x.y.z.w; + ~~~~ +!!! error TS2532: Object is possibly 'undefined'. + ~~~~~~~~ +!!! error TS2532: Object is possibly 'undefined'. interface Base { f(): this is Derived; @@ -88,14 +178,23 @@ tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(62,5): error TS2 x: number; } - declare const o5: Base | undefined; - if (o5?.f()) { - o5; // Derived + declare const o6: Base | undefined; + if (o6?.f()) { + o6; + o6.f; } else { - o5; // Base | undefined + o6; + o6?.f; + o6.f; + ~~ +!!! error TS2532: Object is possibly 'undefined'. } - o5; // Base | undefined + o6; + o6?.f; + o6.f; + ~~ +!!! error TS2532: Object is possibly 'undefined'. // asserts declare const isDefined: (value: T) => asserts value is NonNullable; @@ -106,20 +205,20 @@ tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(62,5): error TS2 function f01(x: unknown) { if (!!true) { isString?.(x); - x; // string + x; } if (!!true) { maybeIsString?.(x); - x; // unknown + x; } if (!!true) { isDefined(maybeIsString); maybeIsString?.(x); - x; // TODO: is unknown, should be string + x; } if (!!true) { maybeNever?.(); - x; // unknown + x; } } \ No newline at end of file diff --git a/tests/baselines/reference/controlFlowOptionalChain.js b/tests/baselines/reference/controlFlowOptionalChain.js index 26f5f1bae4dc2..9ca865e711f96 100644 --- a/tests/baselines/reference/controlFlowOptionalChain.js +++ b/tests/baselines/reference/controlFlowOptionalChain.js @@ -26,42 +26,91 @@ d.toString(); declare const f: undefined | ((x: any) => x is number); declare const x: string | number; if (f?.(x)) { - x; // number - f; // TODO: still possibly undefined, should be defined + x; + f; + f(x); } else { - x; // string | number - f; // still possibly undefined + x; + f; + f(x); } x; f; +f(x); declare const o2: { f(x: any): x is number; } | undefined; if (o2?.f(x)) { - x; // number - o2; // TODO: still possibly undefined, should be defined + x; + o2.f; + o2?.f; + o2?.f(x); } else { - x; // string | number - o2; // still possibly undefined. + x; + o2; + o2?.f; + o2.f; } x; o2; +o2?.f; +o2.f; declare const o3: { x: 1, y: string } | { x: 2, y: number } | undefined; if (o3?.x === 1) { - o3; // TODO: still possibly undefined, should be defined + o3; + o3.x; + o3?.x; } else { o3; + o3?.x; + o3.x; } o3; +o3?.x; +o3.x; declare const o4: { x?: { y: boolean } }; if (o4.x?.y) { - o4.x; // TODO: still possibly undefined, should be defined + o4.x; + o4.x?.y; + o4.x.y; +} +else { + o4.x; + o4.x?.y; o4.x.y; } +o4.x; +o4.x?.y; +o4.x.y; + +declare const o5: { x?: { y: { z?: { w: boolean } } } }; +if (o5.x?.y.z?.w) { + o5.x; + o5.x.y; + o5.x.y.z; + o5.x.y.z.w; + o5.x.y.z?.w; + o5.x?.y.z.w; + o5.x?.y.z?.w; +} +else { + o5.x; + o5.x?.y; + o5.x?.y.z; + o5.x?.y.z?.w; + o5.x.y; + o5.x.y.z.w; +} +o5.x; +o5.x?.y; +o5.x?.y.z; +o5.x?.y.z?.w; +o5.x.y; +o5.x.y.z.w; interface Base { f(): this is Derived; @@ -71,14 +120,19 @@ interface Derived extends Base { x: number; } -declare const o5: Base | undefined; -if (o5?.f()) { - o5; // Derived +declare const o6: Base | undefined; +if (o6?.f()) { + o6; + o6.f; } else { - o5; // Base | undefined + o6; + o6?.f; + o6.f; } -o5; // Base | undefined +o6; +o6?.f; +o6.f; // asserts declare const isDefined: (value: T) => asserts value is NonNullable; @@ -89,27 +143,27 @@ declare const maybeNever: undefined | (() => never); function f01(x: unknown) { if (!!true) { isString?.(x); - x; // string + x; } if (!!true) { maybeIsString?.(x); - x; // unknown + x; } if (!!true) { isDefined(maybeIsString); maybeIsString?.(x); - x; // TODO: is unknown, should be string + x; } if (!!true) { maybeNever?.(); - x; // unknown + x; } } //// [controlFlowOptionalChain.js] "use strict"; -var _a, _b, _c, _d, _e, _f, _g, _h, _j; +var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10; var a; (_a = o) === null || _a === void 0 ? void 0 : _a[a = 1]; a.toString(); @@ -123,60 +177,112 @@ var d; (_d = o) === null || _d === void 0 ? void 0 : _d.x(d = 1); d.toString(); if ((_e = f) === null || _e === void 0 ? void 0 : _e(x)) { - x; // number - f; // TODO: still possibly undefined, should be defined + x; + f; + f(x); } else { - x; // string | number - f; // still possibly undefined + x; + f; + f(x); } x; f; +f(x); if ((_f = o2) === null || _f === void 0 ? void 0 : _f.f(x)) { - x; // number - o2; // TODO: still possibly undefined, should be defined + x; + o2.f; + (_g = o2) === null || _g === void 0 ? void 0 : _g.f; + (_h = o2) === null || _h === void 0 ? void 0 : _h.f(x); } else { - x; // string | number - o2; // still possibly undefined. + x; + o2; + (_j = o2) === null || _j === void 0 ? void 0 : _j.f; + o2.f; } x; o2; -if (((_g = o3) === null || _g === void 0 ? void 0 : _g.x) === 1) { - o3; // TODO: still possibly undefined, should be defined +(_k = o2) === null || _k === void 0 ? void 0 : _k.f; +o2.f; +if (((_l = o3) === null || _l === void 0 ? void 0 : _l.x) === 1) { + o3; + o3.x; + (_m = o3) === null || _m === void 0 ? void 0 : _m.x; } else { o3; + (_o = o3) === null || _o === void 0 ? void 0 : _o.x; + o3.x; } o3; -if ((_h = o4.x) === null || _h === void 0 ? void 0 : _h.y) { - o4.x; // TODO: still possibly undefined, should be defined +(_p = o3) === null || _p === void 0 ? void 0 : _p.x; +o3.x; +if ((_q = o4.x) === null || _q === void 0 ? void 0 : _q.y) { + o4.x; + (_r = o4.x) === null || _r === void 0 ? void 0 : _r.y; + o4.x.y; +} +else { + o4.x; + (_s = o4.x) === null || _s === void 0 ? void 0 : _s.y; o4.x.y; } -if ((_j = o5) === null || _j === void 0 ? void 0 : _j.f()) { - o5; // Derived +o4.x; +(_t = o4.x) === null || _t === void 0 ? void 0 : _t.y; +o4.x.y; +if ((_v = (_u = o5.x) === null || _u === void 0 ? void 0 : _u.y.z) === null || _v === void 0 ? void 0 : _v.w) { + o5.x; + o5.x.y; + o5.x.y.z; + o5.x.y.z.w; + (_w = o5.x.y.z) === null || _w === void 0 ? void 0 : _w.w; + (_x = o5.x) === null || _x === void 0 ? void 0 : _x.y.z.w; + (_z = (_y = o5.x) === null || _y === void 0 ? void 0 : _y.y.z) === null || _z === void 0 ? void 0 : _z.w; +} +else { + o5.x; + (_0 = o5.x) === null || _0 === void 0 ? void 0 : _0.y; + (_1 = o5.x) === null || _1 === void 0 ? void 0 : _1.y.z; + (_3 = (_2 = o5.x) === null || _2 === void 0 ? void 0 : _2.y.z) === null || _3 === void 0 ? void 0 : _3.w; + o5.x.y; + o5.x.y.z.w; +} +o5.x; +(_4 = o5.x) === null || _4 === void 0 ? void 0 : _4.y; +(_5 = o5.x) === null || _5 === void 0 ? void 0 : _5.y.z; +(_7 = (_6 = o5.x) === null || _6 === void 0 ? void 0 : _6.y.z) === null || _7 === void 0 ? void 0 : _7.w; +o5.x.y; +o5.x.y.z.w; +if ((_8 = o6) === null || _8 === void 0 ? void 0 : _8.f()) { + o6; + o6.f; } else { - o5; // Base | undefined + o6; + (_9 = o6) === null || _9 === void 0 ? void 0 : _9.f; + o6.f; } -o5; // Base | undefined +o6; +(_10 = o6) === null || _10 === void 0 ? void 0 : _10.f; +o6.f; function f01(x) { var _a, _b, _c, _d; if (!!true) { (_a = isString) === null || _a === void 0 ? void 0 : _a(x); - x; // string + x; } if (!!true) { (_b = maybeIsString) === null || _b === void 0 ? void 0 : _b(x); - x; // unknown + x; } if (!!true) { isDefined(maybeIsString); (_c = maybeIsString) === null || _c === void 0 ? void 0 : _c(x); - x; // TODO: is unknown, should be string + x; } if (!!true) { (_d = maybeNever) === null || _d === void 0 ? void 0 : _d(); - x; // unknown + x; } } diff --git a/tests/baselines/reference/controlFlowOptionalChain.symbols b/tests/baselines/reference/controlFlowOptionalChain.symbols index 7c02a33f766ff..92212539c4d6d 100644 --- a/tests/baselines/reference/controlFlowOptionalChain.symbols +++ b/tests/baselines/reference/controlFlowOptionalChain.symbols @@ -75,18 +75,26 @@ if (f?.(x)) { >f : Symbol(f, Decl(controlFlowOptionalChain.ts, 24, 13)) >x : Symbol(x, Decl(controlFlowOptionalChain.ts, 25, 13)) - x; // number + x; >x : Symbol(x, Decl(controlFlowOptionalChain.ts, 25, 13)) - f; // TODO: still possibly undefined, should be defined + f; >f : Symbol(f, Decl(controlFlowOptionalChain.ts, 24, 13)) + + f(x); +>f : Symbol(f, Decl(controlFlowOptionalChain.ts, 24, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 25, 13)) } else { - x; // string | number + x; >x : Symbol(x, Decl(controlFlowOptionalChain.ts, 25, 13)) - f; // still possibly undefined + f; >f : Symbol(f, Decl(controlFlowOptionalChain.ts, 24, 13)) + + f(x); +>f : Symbol(f, Decl(controlFlowOptionalChain.ts, 24, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 25, 13)) } x; >x : Symbol(x, Decl(controlFlowOptionalChain.ts, 25, 13)) @@ -94,180 +102,501 @@ x; f; >f : Symbol(f, Decl(controlFlowOptionalChain.ts, 24, 13)) +f(x); +>f : Symbol(f, Decl(controlFlowOptionalChain.ts, 24, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 25, 13)) + declare const o2: { f(x: any): x is number; } | undefined; ->o2 : Symbol(o2, Decl(controlFlowOptionalChain.ts, 37, 13)) ->f : Symbol(f, Decl(controlFlowOptionalChain.ts, 37, 19)) ->x : Symbol(x, Decl(controlFlowOptionalChain.ts, 37, 22)) ->x : Symbol(x, Decl(controlFlowOptionalChain.ts, 37, 22)) +>o2 : Symbol(o2, Decl(controlFlowOptionalChain.ts, 40, 13)) +>f : Symbol(f, Decl(controlFlowOptionalChain.ts, 40, 19)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 40, 22)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 40, 22)) if (o2?.f(x)) { ->o2?.f : Symbol(f, Decl(controlFlowOptionalChain.ts, 37, 19)) ->o2 : Symbol(o2, Decl(controlFlowOptionalChain.ts, 37, 13)) ->f : Symbol(f, Decl(controlFlowOptionalChain.ts, 37, 19)) +>o2?.f : Symbol(f, Decl(controlFlowOptionalChain.ts, 40, 19)) +>o2 : Symbol(o2, Decl(controlFlowOptionalChain.ts, 40, 13)) +>f : Symbol(f, Decl(controlFlowOptionalChain.ts, 40, 19)) >x : Symbol(x, Decl(controlFlowOptionalChain.ts, 25, 13)) - x; // number + x; >x : Symbol(x, Decl(controlFlowOptionalChain.ts, 25, 13)) - o2; // TODO: still possibly undefined, should be defined ->o2 : Symbol(o2, Decl(controlFlowOptionalChain.ts, 37, 13)) + o2.f; +>o2.f : Symbol(f, Decl(controlFlowOptionalChain.ts, 40, 19)) +>o2 : Symbol(o2, Decl(controlFlowOptionalChain.ts, 40, 13)) +>f : Symbol(f, Decl(controlFlowOptionalChain.ts, 40, 19)) + + o2?.f; +>o2?.f : Symbol(f, Decl(controlFlowOptionalChain.ts, 40, 19)) +>o2 : Symbol(o2, Decl(controlFlowOptionalChain.ts, 40, 13)) +>f : Symbol(f, Decl(controlFlowOptionalChain.ts, 40, 19)) + + o2?.f(x); +>o2?.f : Symbol(f, Decl(controlFlowOptionalChain.ts, 40, 19)) +>o2 : Symbol(o2, Decl(controlFlowOptionalChain.ts, 40, 13)) +>f : Symbol(f, Decl(controlFlowOptionalChain.ts, 40, 19)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 25, 13)) } else { - x; // string | number + x; >x : Symbol(x, Decl(controlFlowOptionalChain.ts, 25, 13)) - o2; // still possibly undefined. ->o2 : Symbol(o2, Decl(controlFlowOptionalChain.ts, 37, 13)) + o2; +>o2 : Symbol(o2, Decl(controlFlowOptionalChain.ts, 40, 13)) + + o2?.f; +>o2?.f : Symbol(f, Decl(controlFlowOptionalChain.ts, 40, 19)) +>o2 : Symbol(o2, Decl(controlFlowOptionalChain.ts, 40, 13)) +>f : Symbol(f, Decl(controlFlowOptionalChain.ts, 40, 19)) + + o2.f; +>o2.f : Symbol(f, Decl(controlFlowOptionalChain.ts, 40, 19)) +>o2 : Symbol(o2, Decl(controlFlowOptionalChain.ts, 40, 13)) +>f : Symbol(f, Decl(controlFlowOptionalChain.ts, 40, 19)) } x; >x : Symbol(x, Decl(controlFlowOptionalChain.ts, 25, 13)) o2; ->o2 : Symbol(o2, Decl(controlFlowOptionalChain.ts, 37, 13)) +>o2 : Symbol(o2, Decl(controlFlowOptionalChain.ts, 40, 13)) + +o2?.f; +>o2?.f : Symbol(f, Decl(controlFlowOptionalChain.ts, 40, 19)) +>o2 : Symbol(o2, Decl(controlFlowOptionalChain.ts, 40, 13)) +>f : Symbol(f, Decl(controlFlowOptionalChain.ts, 40, 19)) + +o2.f; +>o2.f : Symbol(f, Decl(controlFlowOptionalChain.ts, 40, 19)) +>o2 : Symbol(o2, Decl(controlFlowOptionalChain.ts, 40, 13)) +>f : Symbol(f, Decl(controlFlowOptionalChain.ts, 40, 19)) declare const o3: { x: 1, y: string } | { x: 2, y: number } | undefined; ->o3 : Symbol(o3, Decl(controlFlowOptionalChain.ts, 49, 13)) ->x : Symbol(x, Decl(controlFlowOptionalChain.ts, 49, 19)) ->y : Symbol(y, Decl(controlFlowOptionalChain.ts, 49, 25)) ->x : Symbol(x, Decl(controlFlowOptionalChain.ts, 49, 41)) ->y : Symbol(y, Decl(controlFlowOptionalChain.ts, 49, 47)) +>o3 : Symbol(o3, Decl(controlFlowOptionalChain.ts, 58, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 58, 19)) +>y : Symbol(y, Decl(controlFlowOptionalChain.ts, 58, 25)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 58, 41)) +>y : Symbol(y, Decl(controlFlowOptionalChain.ts, 58, 47)) if (o3?.x === 1) { ->o3?.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 49, 19), Decl(controlFlowOptionalChain.ts, 49, 41)) ->o3 : Symbol(o3, Decl(controlFlowOptionalChain.ts, 49, 13)) ->x : Symbol(x, Decl(controlFlowOptionalChain.ts, 49, 19), Decl(controlFlowOptionalChain.ts, 49, 41)) +>o3?.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 58, 19), Decl(controlFlowOptionalChain.ts, 58, 41)) +>o3 : Symbol(o3, Decl(controlFlowOptionalChain.ts, 58, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 58, 19), Decl(controlFlowOptionalChain.ts, 58, 41)) + + o3; +>o3 : Symbol(o3, Decl(controlFlowOptionalChain.ts, 58, 13)) + + o3.x; +>o3.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 58, 19)) +>o3 : Symbol(o3, Decl(controlFlowOptionalChain.ts, 58, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 58, 19)) - o3; // TODO: still possibly undefined, should be defined ->o3 : Symbol(o3, Decl(controlFlowOptionalChain.ts, 49, 13)) + o3?.x; +>o3?.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 58, 19)) +>o3 : Symbol(o3, Decl(controlFlowOptionalChain.ts, 58, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 58, 19)) } else { o3; ->o3 : Symbol(o3, Decl(controlFlowOptionalChain.ts, 49, 13)) +>o3 : Symbol(o3, Decl(controlFlowOptionalChain.ts, 58, 13)) + + o3?.x; +>o3?.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 58, 19), Decl(controlFlowOptionalChain.ts, 58, 41)) +>o3 : Symbol(o3, Decl(controlFlowOptionalChain.ts, 58, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 58, 19), Decl(controlFlowOptionalChain.ts, 58, 41)) + + o3.x; +>o3.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 58, 19), Decl(controlFlowOptionalChain.ts, 58, 41)) +>o3 : Symbol(o3, Decl(controlFlowOptionalChain.ts, 58, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 58, 19), Decl(controlFlowOptionalChain.ts, 58, 41)) } o3; ->o3 : Symbol(o3, Decl(controlFlowOptionalChain.ts, 49, 13)) +>o3 : Symbol(o3, Decl(controlFlowOptionalChain.ts, 58, 13)) + +o3?.x; +>o3?.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 58, 19), Decl(controlFlowOptionalChain.ts, 58, 41)) +>o3 : Symbol(o3, Decl(controlFlowOptionalChain.ts, 58, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 58, 19), Decl(controlFlowOptionalChain.ts, 58, 41)) + +o3.x; +>o3.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 58, 19), Decl(controlFlowOptionalChain.ts, 58, 41)) +>o3 : Symbol(o3, Decl(controlFlowOptionalChain.ts, 58, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 58, 19), Decl(controlFlowOptionalChain.ts, 58, 41)) declare const o4: { x?: { y: boolean } }; ->o4 : Symbol(o4, Decl(controlFlowOptionalChain.ts, 58, 13)) ->x : Symbol(x, Decl(controlFlowOptionalChain.ts, 58, 19)) ->y : Symbol(y, Decl(controlFlowOptionalChain.ts, 58, 25)) +>o4 : Symbol(o4, Decl(controlFlowOptionalChain.ts, 73, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 73, 19)) +>y : Symbol(y, Decl(controlFlowOptionalChain.ts, 73, 25)) if (o4.x?.y) { ->o4.x?.y : Symbol(y, Decl(controlFlowOptionalChain.ts, 58, 25)) ->o4.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 58, 19)) ->o4 : Symbol(o4, Decl(controlFlowOptionalChain.ts, 58, 13)) ->x : Symbol(x, Decl(controlFlowOptionalChain.ts, 58, 19)) ->y : Symbol(y, Decl(controlFlowOptionalChain.ts, 58, 25)) +>o4.x?.y : Symbol(y, Decl(controlFlowOptionalChain.ts, 73, 25)) +>o4.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 73, 19)) +>o4 : Symbol(o4, Decl(controlFlowOptionalChain.ts, 73, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 73, 19)) +>y : Symbol(y, Decl(controlFlowOptionalChain.ts, 73, 25)) + + o4.x; +>o4.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 73, 19)) +>o4 : Symbol(o4, Decl(controlFlowOptionalChain.ts, 73, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 73, 19)) + + o4.x?.y; +>o4.x?.y : Symbol(y, Decl(controlFlowOptionalChain.ts, 73, 25)) +>o4.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 73, 19)) +>o4 : Symbol(o4, Decl(controlFlowOptionalChain.ts, 73, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 73, 19)) +>y : Symbol(y, Decl(controlFlowOptionalChain.ts, 73, 25)) - o4.x; // TODO: still possibly undefined, should be defined ->o4.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 58, 19)) ->o4 : Symbol(o4, Decl(controlFlowOptionalChain.ts, 58, 13)) ->x : Symbol(x, Decl(controlFlowOptionalChain.ts, 58, 19)) + o4.x.y; +>o4.x.y : Symbol(y, Decl(controlFlowOptionalChain.ts, 73, 25)) +>o4.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 73, 19)) +>o4 : Symbol(o4, Decl(controlFlowOptionalChain.ts, 73, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 73, 19)) +>y : Symbol(y, Decl(controlFlowOptionalChain.ts, 73, 25)) +} +else { + o4.x; +>o4.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 73, 19)) +>o4 : Symbol(o4, Decl(controlFlowOptionalChain.ts, 73, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 73, 19)) + + o4.x?.y; +>o4.x?.y : Symbol(y, Decl(controlFlowOptionalChain.ts, 73, 25)) +>o4.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 73, 19)) +>o4 : Symbol(o4, Decl(controlFlowOptionalChain.ts, 73, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 73, 19)) +>y : Symbol(y, Decl(controlFlowOptionalChain.ts, 73, 25)) o4.x.y; ->o4.x.y : Symbol(y, Decl(controlFlowOptionalChain.ts, 58, 25)) ->o4.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 58, 19)) ->o4 : Symbol(o4, Decl(controlFlowOptionalChain.ts, 58, 13)) ->x : Symbol(x, Decl(controlFlowOptionalChain.ts, 58, 19)) ->y : Symbol(y, Decl(controlFlowOptionalChain.ts, 58, 25)) +>o4.x.y : Symbol(y, Decl(controlFlowOptionalChain.ts, 73, 25)) +>o4.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 73, 19)) +>o4 : Symbol(o4, Decl(controlFlowOptionalChain.ts, 73, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 73, 19)) +>y : Symbol(y, Decl(controlFlowOptionalChain.ts, 73, 25)) +} +o4.x; +>o4.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 73, 19)) +>o4 : Symbol(o4, Decl(controlFlowOptionalChain.ts, 73, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 73, 19)) + +o4.x?.y; +>o4.x?.y : Symbol(y, Decl(controlFlowOptionalChain.ts, 73, 25)) +>o4.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 73, 19)) +>o4 : Symbol(o4, Decl(controlFlowOptionalChain.ts, 73, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 73, 19)) +>y : Symbol(y, Decl(controlFlowOptionalChain.ts, 73, 25)) + +o4.x.y; +>o4.x.y : Symbol(y, Decl(controlFlowOptionalChain.ts, 73, 25)) +>o4.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 73, 19)) +>o4 : Symbol(o4, Decl(controlFlowOptionalChain.ts, 73, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 73, 19)) +>y : Symbol(y, Decl(controlFlowOptionalChain.ts, 73, 25)) + +declare const o5: { x?: { y: { z?: { w: boolean } } } }; +>o5 : Symbol(o5, Decl(controlFlowOptionalChain.ts, 88, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 88, 19)) +>y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) +>z : Symbol(z, Decl(controlFlowOptionalChain.ts, 88, 30)) +>w : Symbol(w, Decl(controlFlowOptionalChain.ts, 88, 36)) + +if (o5.x?.y.z?.w) { +>o5.x?.y.z?.w : Symbol(w, Decl(controlFlowOptionalChain.ts, 88, 36)) +>o5.x?.y.z : Symbol(z, Decl(controlFlowOptionalChain.ts, 88, 30)) +>o5.x?.y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) +>o5.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 88, 19)) +>o5 : Symbol(o5, Decl(controlFlowOptionalChain.ts, 88, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 88, 19)) +>y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) +>z : Symbol(z, Decl(controlFlowOptionalChain.ts, 88, 30)) +>w : Symbol(w, Decl(controlFlowOptionalChain.ts, 88, 36)) + + o5.x; +>o5.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 88, 19)) +>o5 : Symbol(o5, Decl(controlFlowOptionalChain.ts, 88, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 88, 19)) + + o5.x.y; +>o5.x.y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) +>o5.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 88, 19)) +>o5 : Symbol(o5, Decl(controlFlowOptionalChain.ts, 88, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 88, 19)) +>y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) + + o5.x.y.z; +>o5.x.y.z : Symbol(z, Decl(controlFlowOptionalChain.ts, 88, 30)) +>o5.x.y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) +>o5.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 88, 19)) +>o5 : Symbol(o5, Decl(controlFlowOptionalChain.ts, 88, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 88, 19)) +>y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) +>z : Symbol(z, Decl(controlFlowOptionalChain.ts, 88, 30)) + + o5.x.y.z.w; +>o5.x.y.z.w : Symbol(w, Decl(controlFlowOptionalChain.ts, 88, 36)) +>o5.x.y.z : Symbol(z, Decl(controlFlowOptionalChain.ts, 88, 30)) +>o5.x.y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) +>o5.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 88, 19)) +>o5 : Symbol(o5, Decl(controlFlowOptionalChain.ts, 88, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 88, 19)) +>y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) +>z : Symbol(z, Decl(controlFlowOptionalChain.ts, 88, 30)) +>w : Symbol(w, Decl(controlFlowOptionalChain.ts, 88, 36)) + + o5.x.y.z?.w; +>o5.x.y.z?.w : Symbol(w, Decl(controlFlowOptionalChain.ts, 88, 36)) +>o5.x.y.z : Symbol(z, Decl(controlFlowOptionalChain.ts, 88, 30)) +>o5.x.y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) +>o5.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 88, 19)) +>o5 : Symbol(o5, Decl(controlFlowOptionalChain.ts, 88, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 88, 19)) +>y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) +>z : Symbol(z, Decl(controlFlowOptionalChain.ts, 88, 30)) +>w : Symbol(w, Decl(controlFlowOptionalChain.ts, 88, 36)) + + o5.x?.y.z.w; +>o5.x?.y.z.w : Symbol(w, Decl(controlFlowOptionalChain.ts, 88, 36)) +>o5.x?.y.z : Symbol(z, Decl(controlFlowOptionalChain.ts, 88, 30)) +>o5.x?.y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) +>o5.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 88, 19)) +>o5 : Symbol(o5, Decl(controlFlowOptionalChain.ts, 88, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 88, 19)) +>y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) +>z : Symbol(z, Decl(controlFlowOptionalChain.ts, 88, 30)) +>w : Symbol(w, Decl(controlFlowOptionalChain.ts, 88, 36)) + + o5.x?.y.z?.w; +>o5.x?.y.z?.w : Symbol(w, Decl(controlFlowOptionalChain.ts, 88, 36)) +>o5.x?.y.z : Symbol(z, Decl(controlFlowOptionalChain.ts, 88, 30)) +>o5.x?.y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) +>o5.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 88, 19)) +>o5 : Symbol(o5, Decl(controlFlowOptionalChain.ts, 88, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 88, 19)) +>y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) +>z : Symbol(z, Decl(controlFlowOptionalChain.ts, 88, 30)) +>w : Symbol(w, Decl(controlFlowOptionalChain.ts, 88, 36)) } +else { + o5.x; +>o5.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 88, 19)) +>o5 : Symbol(o5, Decl(controlFlowOptionalChain.ts, 88, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 88, 19)) + + o5.x?.y; +>o5.x?.y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) +>o5.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 88, 19)) +>o5 : Symbol(o5, Decl(controlFlowOptionalChain.ts, 88, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 88, 19)) +>y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) + + o5.x?.y.z; +>o5.x?.y.z : Symbol(z, Decl(controlFlowOptionalChain.ts, 88, 30)) +>o5.x?.y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) +>o5.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 88, 19)) +>o5 : Symbol(o5, Decl(controlFlowOptionalChain.ts, 88, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 88, 19)) +>y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) +>z : Symbol(z, Decl(controlFlowOptionalChain.ts, 88, 30)) + + o5.x?.y.z?.w; +>o5.x?.y.z?.w : Symbol(w, Decl(controlFlowOptionalChain.ts, 88, 36)) +>o5.x?.y.z : Symbol(z, Decl(controlFlowOptionalChain.ts, 88, 30)) +>o5.x?.y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) +>o5.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 88, 19)) +>o5 : Symbol(o5, Decl(controlFlowOptionalChain.ts, 88, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 88, 19)) +>y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) +>z : Symbol(z, Decl(controlFlowOptionalChain.ts, 88, 30)) +>w : Symbol(w, Decl(controlFlowOptionalChain.ts, 88, 36)) + + o5.x.y; +>o5.x.y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) +>o5.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 88, 19)) +>o5 : Symbol(o5, Decl(controlFlowOptionalChain.ts, 88, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 88, 19)) +>y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) + + o5.x.y.z.w; +>o5.x.y.z.w : Symbol(w, Decl(controlFlowOptionalChain.ts, 88, 36)) +>o5.x.y.z : Symbol(z, Decl(controlFlowOptionalChain.ts, 88, 30)) +>o5.x.y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) +>o5.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 88, 19)) +>o5 : Symbol(o5, Decl(controlFlowOptionalChain.ts, 88, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 88, 19)) +>y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) +>z : Symbol(z, Decl(controlFlowOptionalChain.ts, 88, 30)) +>w : Symbol(w, Decl(controlFlowOptionalChain.ts, 88, 36)) +} +o5.x; +>o5.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 88, 19)) +>o5 : Symbol(o5, Decl(controlFlowOptionalChain.ts, 88, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 88, 19)) + +o5.x?.y; +>o5.x?.y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) +>o5.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 88, 19)) +>o5 : Symbol(o5, Decl(controlFlowOptionalChain.ts, 88, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 88, 19)) +>y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) + +o5.x?.y.z; +>o5.x?.y.z : Symbol(z, Decl(controlFlowOptionalChain.ts, 88, 30)) +>o5.x?.y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) +>o5.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 88, 19)) +>o5 : Symbol(o5, Decl(controlFlowOptionalChain.ts, 88, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 88, 19)) +>y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) +>z : Symbol(z, Decl(controlFlowOptionalChain.ts, 88, 30)) + +o5.x?.y.z?.w; +>o5.x?.y.z?.w : Symbol(w, Decl(controlFlowOptionalChain.ts, 88, 36)) +>o5.x?.y.z : Symbol(z, Decl(controlFlowOptionalChain.ts, 88, 30)) +>o5.x?.y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) +>o5.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 88, 19)) +>o5 : Symbol(o5, Decl(controlFlowOptionalChain.ts, 88, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 88, 19)) +>y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) +>z : Symbol(z, Decl(controlFlowOptionalChain.ts, 88, 30)) +>w : Symbol(w, Decl(controlFlowOptionalChain.ts, 88, 36)) + +o5.x.y; +>o5.x.y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) +>o5.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 88, 19)) +>o5 : Symbol(o5, Decl(controlFlowOptionalChain.ts, 88, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 88, 19)) +>y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) + +o5.x.y.z.w; +>o5.x.y.z.w : Symbol(w, Decl(controlFlowOptionalChain.ts, 88, 36)) +>o5.x.y.z : Symbol(z, Decl(controlFlowOptionalChain.ts, 88, 30)) +>o5.x.y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) +>o5.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 88, 19)) +>o5 : Symbol(o5, Decl(controlFlowOptionalChain.ts, 88, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 88, 19)) +>y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) +>z : Symbol(z, Decl(controlFlowOptionalChain.ts, 88, 30)) +>w : Symbol(w, Decl(controlFlowOptionalChain.ts, 88, 36)) interface Base { ->Base : Symbol(Base, Decl(controlFlowOptionalChain.ts, 62, 1)) +>Base : Symbol(Base, Decl(controlFlowOptionalChain.ts, 111, 11)) f(): this is Derived; ->f : Symbol(Base.f, Decl(controlFlowOptionalChain.ts, 64, 16)) ->Derived : Symbol(Derived, Decl(controlFlowOptionalChain.ts, 66, 1)) +>f : Symbol(Base.f, Decl(controlFlowOptionalChain.ts, 113, 16)) +>Derived : Symbol(Derived, Decl(controlFlowOptionalChain.ts, 115, 1)) } interface Derived extends Base { ->Derived : Symbol(Derived, Decl(controlFlowOptionalChain.ts, 66, 1)) ->Base : Symbol(Base, Decl(controlFlowOptionalChain.ts, 62, 1)) +>Derived : Symbol(Derived, Decl(controlFlowOptionalChain.ts, 115, 1)) +>Base : Symbol(Base, Decl(controlFlowOptionalChain.ts, 111, 11)) x: number; ->x : Symbol(Derived.x, Decl(controlFlowOptionalChain.ts, 68, 32)) +>x : Symbol(Derived.x, Decl(controlFlowOptionalChain.ts, 117, 32)) } -declare const o5: Base | undefined; ->o5 : Symbol(o5, Decl(controlFlowOptionalChain.ts, 72, 13)) ->Base : Symbol(Base, Decl(controlFlowOptionalChain.ts, 62, 1)) +declare const o6: Base | undefined; +>o6 : Symbol(o6, Decl(controlFlowOptionalChain.ts, 121, 13)) +>Base : Symbol(Base, Decl(controlFlowOptionalChain.ts, 111, 11)) + +if (o6?.f()) { +>o6?.f : Symbol(Base.f, Decl(controlFlowOptionalChain.ts, 113, 16)) +>o6 : Symbol(o6, Decl(controlFlowOptionalChain.ts, 121, 13)) +>f : Symbol(Base.f, Decl(controlFlowOptionalChain.ts, 113, 16)) -if (o5?.f()) { ->o5?.f : Symbol(Base.f, Decl(controlFlowOptionalChain.ts, 64, 16)) ->o5 : Symbol(o5, Decl(controlFlowOptionalChain.ts, 72, 13)) ->f : Symbol(Base.f, Decl(controlFlowOptionalChain.ts, 64, 16)) + o6; +>o6 : Symbol(o6, Decl(controlFlowOptionalChain.ts, 121, 13)) - o5; // Derived ->o5 : Symbol(o5, Decl(controlFlowOptionalChain.ts, 72, 13)) + o6.f; +>o6.f : Symbol(Base.f, Decl(controlFlowOptionalChain.ts, 113, 16)) +>o6 : Symbol(o6, Decl(controlFlowOptionalChain.ts, 121, 13)) +>f : Symbol(Base.f, Decl(controlFlowOptionalChain.ts, 113, 16)) } else { - o5; // Base | undefined ->o5 : Symbol(o5, Decl(controlFlowOptionalChain.ts, 72, 13)) + o6; +>o6 : Symbol(o6, Decl(controlFlowOptionalChain.ts, 121, 13)) + + o6?.f; +>o6?.f : Symbol(Base.f, Decl(controlFlowOptionalChain.ts, 113, 16)) +>o6 : Symbol(o6, Decl(controlFlowOptionalChain.ts, 121, 13)) +>f : Symbol(Base.f, Decl(controlFlowOptionalChain.ts, 113, 16)) + + o6.f; +>o6.f : Symbol(Base.f, Decl(controlFlowOptionalChain.ts, 113, 16)) +>o6 : Symbol(o6, Decl(controlFlowOptionalChain.ts, 121, 13)) +>f : Symbol(Base.f, Decl(controlFlowOptionalChain.ts, 113, 16)) } -o5; // Base | undefined ->o5 : Symbol(o5, Decl(controlFlowOptionalChain.ts, 72, 13)) +o6; +>o6 : Symbol(o6, Decl(controlFlowOptionalChain.ts, 121, 13)) + +o6?.f; +>o6?.f : Symbol(Base.f, Decl(controlFlowOptionalChain.ts, 113, 16)) +>o6 : Symbol(o6, Decl(controlFlowOptionalChain.ts, 121, 13)) +>f : Symbol(Base.f, Decl(controlFlowOptionalChain.ts, 113, 16)) + +o6.f; +>o6.f : Symbol(Base.f, Decl(controlFlowOptionalChain.ts, 113, 16)) +>o6 : Symbol(o6, Decl(controlFlowOptionalChain.ts, 121, 13)) +>f : Symbol(Base.f, Decl(controlFlowOptionalChain.ts, 113, 16)) // asserts declare const isDefined: (value: T) => asserts value is NonNullable; ->isDefined : Symbol(isDefined, Decl(controlFlowOptionalChain.ts, 82, 13)) ->T : Symbol(T, Decl(controlFlowOptionalChain.ts, 82, 26)) ->value : Symbol(value, Decl(controlFlowOptionalChain.ts, 82, 29)) ->T : Symbol(T, Decl(controlFlowOptionalChain.ts, 82, 26)) ->value : Symbol(value, Decl(controlFlowOptionalChain.ts, 82, 29)) +>isDefined : Symbol(isDefined, Decl(controlFlowOptionalChain.ts, 136, 13)) +>T : Symbol(T, Decl(controlFlowOptionalChain.ts, 136, 26)) +>value : Symbol(value, Decl(controlFlowOptionalChain.ts, 136, 29)) +>T : Symbol(T, Decl(controlFlowOptionalChain.ts, 136, 26)) +>value : Symbol(value, Decl(controlFlowOptionalChain.ts, 136, 29)) >NonNullable : Symbol(NonNullable, Decl(lib.es5.d.ts, --, --)) ->T : Symbol(T, Decl(controlFlowOptionalChain.ts, 82, 26)) +>T : Symbol(T, Decl(controlFlowOptionalChain.ts, 136, 26)) declare const isString: (value: unknown) => asserts value is string; ->isString : Symbol(isString, Decl(controlFlowOptionalChain.ts, 83, 13)) ->value : Symbol(value, Decl(controlFlowOptionalChain.ts, 83, 25)) ->value : Symbol(value, Decl(controlFlowOptionalChain.ts, 83, 25)) +>isString : Symbol(isString, Decl(controlFlowOptionalChain.ts, 137, 13)) +>value : Symbol(value, Decl(controlFlowOptionalChain.ts, 137, 25)) +>value : Symbol(value, Decl(controlFlowOptionalChain.ts, 137, 25)) declare const maybeIsString: undefined | ((value: unknown) => asserts value is string); ->maybeIsString : Symbol(maybeIsString, Decl(controlFlowOptionalChain.ts, 84, 13)) ->value : Symbol(value, Decl(controlFlowOptionalChain.ts, 84, 43)) ->value : Symbol(value, Decl(controlFlowOptionalChain.ts, 84, 43)) +>maybeIsString : Symbol(maybeIsString, Decl(controlFlowOptionalChain.ts, 138, 13)) +>value : Symbol(value, Decl(controlFlowOptionalChain.ts, 138, 43)) +>value : Symbol(value, Decl(controlFlowOptionalChain.ts, 138, 43)) declare const maybeNever: undefined | (() => never); ->maybeNever : Symbol(maybeNever, Decl(controlFlowOptionalChain.ts, 85, 13)) +>maybeNever : Symbol(maybeNever, Decl(controlFlowOptionalChain.ts, 139, 13)) function f01(x: unknown) { ->f01 : Symbol(f01, Decl(controlFlowOptionalChain.ts, 85, 52)) ->x : Symbol(x, Decl(controlFlowOptionalChain.ts, 87, 13)) +>f01 : Symbol(f01, Decl(controlFlowOptionalChain.ts, 139, 52)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 141, 13)) if (!!true) { isString?.(x); ->isString : Symbol(isString, Decl(controlFlowOptionalChain.ts, 83, 13)) ->x : Symbol(x, Decl(controlFlowOptionalChain.ts, 87, 13)) +>isString : Symbol(isString, Decl(controlFlowOptionalChain.ts, 137, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 141, 13)) - x; // string ->x : Symbol(x, Decl(controlFlowOptionalChain.ts, 87, 13)) + x; +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 141, 13)) } if (!!true) { maybeIsString?.(x); ->maybeIsString : Symbol(maybeIsString, Decl(controlFlowOptionalChain.ts, 84, 13)) ->x : Symbol(x, Decl(controlFlowOptionalChain.ts, 87, 13)) +>maybeIsString : Symbol(maybeIsString, Decl(controlFlowOptionalChain.ts, 138, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 141, 13)) - x; // unknown ->x : Symbol(x, Decl(controlFlowOptionalChain.ts, 87, 13)) + x; +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 141, 13)) } if (!!true) { isDefined(maybeIsString); ->isDefined : Symbol(isDefined, Decl(controlFlowOptionalChain.ts, 82, 13)) ->maybeIsString : Symbol(maybeIsString, Decl(controlFlowOptionalChain.ts, 84, 13)) +>isDefined : Symbol(isDefined, Decl(controlFlowOptionalChain.ts, 136, 13)) +>maybeIsString : Symbol(maybeIsString, Decl(controlFlowOptionalChain.ts, 138, 13)) maybeIsString?.(x); ->maybeIsString : Symbol(maybeIsString, Decl(controlFlowOptionalChain.ts, 84, 13)) ->x : Symbol(x, Decl(controlFlowOptionalChain.ts, 87, 13)) +>maybeIsString : Symbol(maybeIsString, Decl(controlFlowOptionalChain.ts, 138, 13)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 141, 13)) - x; // TODO: is unknown, should be string ->x : Symbol(x, Decl(controlFlowOptionalChain.ts, 87, 13)) + x; +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 141, 13)) } if (!!true) { maybeNever?.(); ->maybeNever : Symbol(maybeNever, Decl(controlFlowOptionalChain.ts, 85, 13)) +>maybeNever : Symbol(maybeNever, Decl(controlFlowOptionalChain.ts, 139, 13)) - x; // unknown ->x : Symbol(x, Decl(controlFlowOptionalChain.ts, 87, 13)) + x; +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 141, 13)) } } diff --git a/tests/baselines/reference/controlFlowOptionalChain.types b/tests/baselines/reference/controlFlowOptionalChain.types index 046bc5272e06a..c65e8e27fabec 100644 --- a/tests/baselines/reference/controlFlowOptionalChain.types +++ b/tests/baselines/reference/controlFlowOptionalChain.types @@ -95,18 +95,28 @@ if (f?.(x)) { >f : ((x: any) => x is number) | undefined >x : string | number - x; // number + x; >x : number - f; // TODO: still possibly undefined, should be defined ->f : ((x: any) => x is number) | undefined + f; +>f : (x: any) => x is number + + f(x); +>f(x) : boolean +>f : (x: any) => x is number +>x : number } else { - x; // string | number + x; >x : string | number - f; // still possibly undefined + f; +>f : ((x: any) => x is number) | undefined + + f(x); +>f(x) : boolean >f : ((x: any) => x is number) | undefined +>x : string | number } x; >x : string | number @@ -114,6 +124,11 @@ x; f; >f : ((x: any) => x is number) | undefined +f(x); +>f(x) : boolean +>f : ((x: any) => x is number) | undefined +>x : string | number + declare const o2: { f(x: any): x is number; } | undefined; >o2 : { f(x: any): x is number; } | undefined >f : (x: any) => x is number @@ -126,18 +141,42 @@ if (o2?.f(x)) { >f : ((x: any) => x is number) | undefined >x : string | number - x; // number + x; >x : number - o2; // TODO: still possibly undefined, should be defined ->o2 : { f(x: any): x is number; } | undefined + o2.f; +>o2.f : (x: any) => x is number +>o2 : { f(x: any): x is number; } +>f : (x: any) => x is number + + o2?.f; +>o2?.f : (x: any) => x is number +>o2 : { f(x: any): x is number; } +>f : (x: any) => x is number + + o2?.f(x); +>o2?.f(x) : boolean +>o2?.f : (x: any) => x is number +>o2 : { f(x: any): x is number; } +>f : (x: any) => x is number +>x : number } else { - x; // string | number + x; >x : string | number - o2; // still possibly undefined. + o2; +>o2 : { f(x: any): x is number; } | undefined + + o2?.f; +>o2?.f : ((x: any) => x is number) | undefined +>o2 : { f(x: any): x is number; } | undefined +>f : ((x: any) => x is number) | undefined + + o2.f; +>o2.f : (x: any) => x is number >o2 : { f(x: any): x is number; } | undefined +>f : (x: any) => x is number } x; >x : string | number @@ -145,6 +184,16 @@ x; o2; >o2 : { f(x: any): x is number; } | undefined +o2?.f; +>o2?.f : ((x: any) => x is number) | undefined +>o2 : { f(x: any): x is number; } | undefined +>f : ((x: any) => x is number) | undefined + +o2.f; +>o2.f : (x: any) => x is number +>o2 : { f(x: any): x is number; } | undefined +>f : (x: any) => x is number + declare const o3: { x: 1, y: string } | { x: 2, y: number } | undefined; >o3 : { x: 1; y: string; } | { x: 2; y: number; } | undefined >x : 1 @@ -159,16 +208,46 @@ if (o3?.x === 1) { >x : 1 | 2 | undefined >1 : 1 - o3; // TODO: still possibly undefined, should be defined ->o3 : { x: 1; y: string; } | { x: 2; y: number; } | undefined + o3; +>o3 : { x: 1; y: string; } + + o3.x; +>o3.x : 1 +>o3 : { x: 1; y: string; } +>x : 1 + + o3?.x; +>o3?.x : 1 +>o3 : { x: 1; y: string; } +>x : 1 } else { o3; >o3 : { x: 1; y: string; } | { x: 2; y: number; } | undefined + + o3?.x; +>o3?.x : 2 | undefined +>o3 : { x: 1; y: string; } | { x: 2; y: number; } | undefined +>x : 2 | undefined + + o3.x; +>o3.x : 2 +>o3 : { x: 1; y: string; } | { x: 2; y: number; } | undefined +>x : 2 } o3; >o3 : { x: 1; y: string; } | { x: 2; y: number; } | undefined +o3?.x; +>o3?.x : 1 | 2 | undefined +>o3 : { x: 1; y: string; } | { x: 2; y: number; } | undefined +>x : 1 | 2 | undefined + +o3.x; +>o3.x : 1 | 2 +>o3 : { x: 1; y: string; } | { x: 2; y: number; } | undefined +>x : 1 | 2 + declare const o4: { x?: { y: boolean } }; >o4 : { x?: { y: boolean; } | undefined; } >x : { y: boolean; } | undefined @@ -181,18 +260,247 @@ if (o4.x?.y) { >x : { y: boolean; } | undefined >y : boolean | undefined - o4.x; // TODO: still possibly undefined, should be defined + o4.x; +>o4.x : { y: boolean; } +>o4 : { x?: { y: boolean; } | undefined; } +>x : { y: boolean; } + + o4.x?.y; +>o4.x?.y : true +>o4.x : { y: boolean; } +>o4 : { x?: { y: boolean; } | undefined; } +>x : { y: boolean; } +>y : true + + o4.x.y; +>o4.x.y : true +>o4.x : { y: boolean; } +>o4 : { x?: { y: boolean; } | undefined; } +>x : { y: boolean; } +>y : true +} +else { + o4.x; +>o4.x : { y: boolean; } | undefined +>o4 : { x?: { y: boolean; } | undefined; } +>x : { y: boolean; } | undefined + + o4.x?.y; +>o4.x?.y : false | undefined >o4.x : { y: boolean; } | undefined >o4 : { x?: { y: boolean; } | undefined; } >x : { y: boolean; } | undefined +>y : false | undefined o4.x.y; ->o4.x.y : true +>o4.x.y : false >o4.x : { y: boolean; } | undefined >o4 : { x?: { y: boolean; } | undefined; } >x : { y: boolean; } | undefined ->y : true +>y : false } +o4.x; +>o4.x : { y: boolean; } | undefined +>o4 : { x?: { y: boolean; } | undefined; } +>x : { y: boolean; } | undefined + +o4.x?.y; +>o4.x?.y : boolean | undefined +>o4.x : { y: boolean; } | undefined +>o4 : { x?: { y: boolean; } | undefined; } +>x : { y: boolean; } | undefined +>y : boolean | undefined + +o4.x.y; +>o4.x.y : boolean +>o4.x : { y: boolean; } | undefined +>o4 : { x?: { y: boolean; } | undefined; } +>x : { y: boolean; } | undefined +>y : boolean + +declare const o5: { x?: { y: { z?: { w: boolean } } } }; +>o5 : { x?: { y: { z?: { w: boolean; } | undefined; }; } | undefined; } +>x : { y: { z?: { w: boolean; } | undefined; }; } | undefined +>y : { z?: { w: boolean; } | undefined; } +>z : { w: boolean; } | undefined +>w : boolean + +if (o5.x?.y.z?.w) { +>o5.x?.y.z?.w : boolean | undefined +>o5.x?.y.z : { w: boolean; } | undefined +>o5.x?.y : { z?: { w: boolean; } | undefined; } | undefined +>o5.x : { y: { z?: { w: boolean; } | undefined; }; } | undefined +>o5 : { x?: { y: { z?: { w: boolean; } | undefined; }; } | undefined; } +>x : { y: { z?: { w: boolean; } | undefined; }; } | undefined +>y : { z?: { w: boolean; } | undefined; } | undefined +>z : { w: boolean; } | undefined +>w : boolean | undefined + + o5.x; +>o5.x : { y: { z?: { w: boolean; } | undefined; }; } +>o5 : { x?: { y: { z?: { w: boolean; } | undefined; }; } | undefined; } +>x : { y: { z?: { w: boolean; } | undefined; }; } + + o5.x.y; +>o5.x.y : { z?: { w: boolean; } | undefined; } +>o5.x : { y: { z?: { w: boolean; } | undefined; }; } +>o5 : { x?: { y: { z?: { w: boolean; } | undefined; }; } | undefined; } +>x : { y: { z?: { w: boolean; } | undefined; }; } +>y : { z?: { w: boolean; } | undefined; } + + o5.x.y.z; +>o5.x.y.z : { w: boolean; } +>o5.x.y : { z?: { w: boolean; } | undefined; } +>o5.x : { y: { z?: { w: boolean; } | undefined; }; } +>o5 : { x?: { y: { z?: { w: boolean; } | undefined; }; } | undefined; } +>x : { y: { z?: { w: boolean; } | undefined; }; } +>y : { z?: { w: boolean; } | undefined; } +>z : { w: boolean; } + + o5.x.y.z.w; +>o5.x.y.z.w : true +>o5.x.y.z : { w: boolean; } +>o5.x.y : { z?: { w: boolean; } | undefined; } +>o5.x : { y: { z?: { w: boolean; } | undefined; }; } +>o5 : { x?: { y: { z?: { w: boolean; } | undefined; }; } | undefined; } +>x : { y: { z?: { w: boolean; } | undefined; }; } +>y : { z?: { w: boolean; } | undefined; } +>z : { w: boolean; } +>w : true + + o5.x.y.z?.w; +>o5.x.y.z?.w : true +>o5.x.y.z : { w: boolean; } +>o5.x.y : { z?: { w: boolean; } | undefined; } +>o5.x : { y: { z?: { w: boolean; } | undefined; }; } +>o5 : { x?: { y: { z?: { w: boolean; } | undefined; }; } | undefined; } +>x : { y: { z?: { w: boolean; } | undefined; }; } +>y : { z?: { w: boolean; } | undefined; } +>z : { w: boolean; } +>w : true + + o5.x?.y.z.w; +>o5.x?.y.z.w : true +>o5.x?.y.z : { w: boolean; } +>o5.x?.y : { z?: { w: boolean; } | undefined; } +>o5.x : { y: { z?: { w: boolean; } | undefined; }; } +>o5 : { x?: { y: { z?: { w: boolean; } | undefined; }; } | undefined; } +>x : { y: { z?: { w: boolean; } | undefined; }; } +>y : { z?: { w: boolean; } | undefined; } +>z : { w: boolean; } +>w : true + + o5.x?.y.z?.w; +>o5.x?.y.z?.w : true +>o5.x?.y.z : { w: boolean; } +>o5.x?.y : { z?: { w: boolean; } | undefined; } +>o5.x : { y: { z?: { w: boolean; } | undefined; }; } +>o5 : { x?: { y: { z?: { w: boolean; } | undefined; }; } | undefined; } +>x : { y: { z?: { w: boolean; } | undefined; }; } +>y : { z?: { w: boolean; } | undefined; } +>z : { w: boolean; } +>w : true +} +else { + o5.x; +>o5.x : { y: { z?: { w: boolean; } | undefined; }; } | undefined +>o5 : { x?: { y: { z?: { w: boolean; } | undefined; }; } | undefined; } +>x : { y: { z?: { w: boolean; } | undefined; }; } | undefined + + o5.x?.y; +>o5.x?.y : { z?: { w: boolean; } | undefined; } | undefined +>o5.x : { y: { z?: { w: boolean; } | undefined; }; } | undefined +>o5 : { x?: { y: { z?: { w: boolean; } | undefined; }; } | undefined; } +>x : { y: { z?: { w: boolean; } | undefined; }; } | undefined +>y : { z?: { w: boolean; } | undefined; } | undefined + + o5.x?.y.z; +>o5.x?.y.z : { w: boolean; } | undefined +>o5.x?.y : { z?: { w: boolean; } | undefined; } | undefined +>o5.x : { y: { z?: { w: boolean; } | undefined; }; } | undefined +>o5 : { x?: { y: { z?: { w: boolean; } | undefined; }; } | undefined; } +>x : { y: { z?: { w: boolean; } | undefined; }; } | undefined +>y : { z?: { w: boolean; } | undefined; } | undefined +>z : { w: boolean; } | undefined + + o5.x?.y.z?.w; +>o5.x?.y.z?.w : false | undefined +>o5.x?.y.z : { w: boolean; } | undefined +>o5.x?.y : { z?: { w: boolean; } | undefined; } | undefined +>o5.x : { y: { z?: { w: boolean; } | undefined; }; } | undefined +>o5 : { x?: { y: { z?: { w: boolean; } | undefined; }; } | undefined; } +>x : { y: { z?: { w: boolean; } | undefined; }; } | undefined +>y : { z?: { w: boolean; } | undefined; } | undefined +>z : { w: boolean; } | undefined +>w : false | undefined + + o5.x.y; +>o5.x.y : { z?: { w: boolean; } | undefined; } +>o5.x : { y: { z?: { w: boolean; } | undefined; }; } | undefined +>o5 : { x?: { y: { z?: { w: boolean; } | undefined; }; } | undefined; } +>x : { y: { z?: { w: boolean; } | undefined; }; } | undefined +>y : { z?: { w: boolean; } | undefined; } + + o5.x.y.z.w; +>o5.x.y.z.w : false +>o5.x.y.z : { w: boolean; } | undefined +>o5.x.y : { z?: { w: boolean; } | undefined; } +>o5.x : { y: { z?: { w: boolean; } | undefined; }; } | undefined +>o5 : { x?: { y: { z?: { w: boolean; } | undefined; }; } | undefined; } +>x : { y: { z?: { w: boolean; } | undefined; }; } | undefined +>y : { z?: { w: boolean; } | undefined; } +>z : { w: boolean; } | undefined +>w : false +} +o5.x; +>o5.x : { y: { z?: { w: boolean; } | undefined; }; } | undefined +>o5 : { x?: { y: { z?: { w: boolean; } | undefined; }; } | undefined; } +>x : { y: { z?: { w: boolean; } | undefined; }; } | undefined + +o5.x?.y; +>o5.x?.y : { z?: { w: boolean; } | undefined; } | undefined +>o5.x : { y: { z?: { w: boolean; } | undefined; }; } | undefined +>o5 : { x?: { y: { z?: { w: boolean; } | undefined; }; } | undefined; } +>x : { y: { z?: { w: boolean; } | undefined; }; } | undefined +>y : { z?: { w: boolean; } | undefined; } | undefined + +o5.x?.y.z; +>o5.x?.y.z : { w: boolean; } | undefined +>o5.x?.y : { z?: { w: boolean; } | undefined; } | undefined +>o5.x : { y: { z?: { w: boolean; } | undefined; }; } | undefined +>o5 : { x?: { y: { z?: { w: boolean; } | undefined; }; } | undefined; } +>x : { y: { z?: { w: boolean; } | undefined; }; } | undefined +>y : { z?: { w: boolean; } | undefined; } | undefined +>z : { w: boolean; } | undefined + +o5.x?.y.z?.w; +>o5.x?.y.z?.w : boolean | undefined +>o5.x?.y.z : { w: boolean; } | undefined +>o5.x?.y : { z?: { w: boolean; } | undefined; } | undefined +>o5.x : { y: { z?: { w: boolean; } | undefined; }; } | undefined +>o5 : { x?: { y: { z?: { w: boolean; } | undefined; }; } | undefined; } +>x : { y: { z?: { w: boolean; } | undefined; }; } | undefined +>y : { z?: { w: boolean; } | undefined; } | undefined +>z : { w: boolean; } | undefined +>w : boolean | undefined + +o5.x.y; +>o5.x.y : { z?: { w: boolean; } | undefined; } +>o5.x : { y: { z?: { w: boolean; } | undefined; }; } | undefined +>o5 : { x?: { y: { z?: { w: boolean; } | undefined; }; } | undefined; } +>x : { y: { z?: { w: boolean; } | undefined; }; } | undefined +>y : { z?: { w: boolean; } | undefined; } + +o5.x.y.z.w; +>o5.x.y.z.w : boolean +>o5.x.y.z : { w: boolean; } | undefined +>o5.x.y : { z?: { w: boolean; } | undefined; } +>o5.x : { y: { z?: { w: boolean; } | undefined; }; } | undefined +>o5 : { x?: { y: { z?: { w: boolean; } | undefined; }; } | undefined; } +>x : { y: { z?: { w: boolean; } | undefined; }; } | undefined +>y : { z?: { w: boolean; } | undefined; } +>z : { w: boolean; } | undefined +>w : boolean interface Base { f(): this is Derived; @@ -204,24 +512,49 @@ interface Derived extends Base { >x : number } -declare const o5: Base | undefined; ->o5 : Base | undefined +declare const o6: Base | undefined; +>o6 : Base | undefined -if (o5?.f()) { ->o5?.f() : boolean | undefined ->o5?.f : (() => this is Derived) | undefined ->o5 : Base | undefined +if (o6?.f()) { +>o6?.f() : boolean | undefined +>o6?.f : (() => this is Derived) | undefined +>o6 : Base | undefined >f : (() => this is Derived) | undefined - o5; // Derived ->o5 : Derived + o6; +>o6 : Derived + + o6.f; +>o6.f : () => this is Derived +>o6 : Derived +>f : () => this is Derived } else { - o5; // Base | undefined ->o5 : Base | undefined + o6; +>o6 : Base | undefined + + o6?.f; +>o6?.f : (() => this is Derived) | undefined +>o6 : Base | undefined +>f : (() => this is Derived) | undefined + + o6.f; +>o6.f : () => this is Derived +>o6 : Base | undefined +>f : () => this is Derived } -o5; // Base | undefined ->o5 : Base | undefined +o6; +>o6 : Base | undefined + +o6?.f; +>o6?.f : (() => this is Derived) | undefined +>o6 : Base | undefined +>f : (() => this is Derived) | undefined + +o6.f; +>o6.f : () => this is Derived +>o6 : Base | undefined +>f : () => this is Derived // asserts declare const isDefined: (value: T) => asserts value is NonNullable; @@ -253,7 +586,7 @@ function f01(x: unknown) { >isString : (value: unknown) => asserts value is string >x : unknown - x; // string + x; >x : string } if (!!true) { @@ -266,7 +599,7 @@ function f01(x: unknown) { >maybeIsString : ((value: unknown) => asserts value is string) | undefined >x : unknown - x; // unknown + x; >x : unknown } if (!!true) { @@ -284,7 +617,7 @@ function f01(x: unknown) { >maybeIsString : (value: unknown) => asserts value is string >x : unknown - x; // TODO: is unknown, should be string + x; >x : unknown } if (!!true) { @@ -296,7 +629,7 @@ function f01(x: unknown) { >maybeNever?.() : undefined >maybeNever : (() => never) | undefined - x; // unknown + x; >x : unknown } } diff --git a/tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts b/tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts index ec7118a1c8db0..9cfed0fcb959f 100644 --- a/tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts +++ b/tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts @@ -28,42 +28,91 @@ d.toString(); declare const f: undefined | ((x: any) => x is number); declare const x: string | number; if (f?.(x)) { - x; // number - f; // TODO: still possibly undefined, should be defined + x; + f; + f(x); } else { - x; // string | number - f; // still possibly undefined + x; + f; + f(x); } x; f; +f(x); declare const o2: { f(x: any): x is number; } | undefined; if (o2?.f(x)) { - x; // number - o2; // TODO: still possibly undefined, should be defined + x; + o2.f; + o2?.f; + o2?.f(x); } else { - x; // string | number - o2; // still possibly undefined. + x; + o2; + o2?.f; + o2.f; } x; o2; +o2?.f; +o2.f; declare const o3: { x: 1, y: string } | { x: 2, y: number } | undefined; if (o3?.x === 1) { - o3; // TODO: still possibly undefined, should be defined + o3; + o3.x; + o3?.x; } else { o3; + o3?.x; + o3.x; } o3; +o3?.x; +o3.x; declare const o4: { x?: { y: boolean } }; if (o4.x?.y) { - o4.x; // TODO: still possibly undefined, should be defined + o4.x; + o4.x?.y; + o4.x.y; +} +else { + o4.x; + o4.x?.y; o4.x.y; } +o4.x; +o4.x?.y; +o4.x.y; + +declare const o5: { x?: { y: { z?: { w: boolean } } } }; +if (o5.x?.y.z?.w) { + o5.x; + o5.x.y; + o5.x.y.z; + o5.x.y.z.w; + o5.x.y.z?.w; + o5.x?.y.z.w; + o5.x?.y.z?.w; +} +else { + o5.x; + o5.x?.y; + o5.x?.y.z; + o5.x?.y.z?.w; + o5.x.y; + o5.x.y.z.w; +} +o5.x; +o5.x?.y; +o5.x?.y.z; +o5.x?.y.z?.w; +o5.x.y; +o5.x.y.z.w; interface Base { f(): this is Derived; @@ -73,14 +122,19 @@ interface Derived extends Base { x: number; } -declare const o5: Base | undefined; -if (o5?.f()) { - o5; // Derived +declare const o6: Base | undefined; +if (o6?.f()) { + o6; + o6.f; } else { - o5; // Base | undefined + o6; + o6?.f; + o6.f; } -o5; // Base | undefined +o6; +o6?.f; +o6.f; // asserts declare const isDefined: (value: T) => asserts value is NonNullable; @@ -91,19 +145,19 @@ declare const maybeNever: undefined | (() => never); function f01(x: unknown) { if (!!true) { isString?.(x); - x; // string + x; } if (!!true) { maybeIsString?.(x); - x; // unknown + x; } if (!!true) { isDefined(maybeIsString); maybeIsString?.(x); - x; // TODO: is unknown, should be string + x; } if (!!true) { maybeNever?.(); - x; // unknown + x; } } From fd8c0d4b736da6a7244edf7adae75446a7da2aaa Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Thu, 26 Sep 2019 16:53:37 -0700 Subject: [PATCH 11/19] Remove mangled smart quotes in comments --- src/compiler/checker.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f037f409883a5..fef449dc610c8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1178,7 +1178,7 @@ namespace ts { // obtain item referenced by 'export=' mainModule = resolveExternalModuleSymbol(mainModule); if (mainModule.flags & SymbolFlags.Namespace) { - // If we’re merging an augmentation to a pattern ambient module, we want to + // If we're merging an augmentation to a pattern ambient module, we want to // perform the merge unidirectionally from the augmentation ('a.foo') to // the pattern ('*.foo'), so that 'getMergedSymbol()' on a.foo gives you // all the exports both from the pattern and from the augmentation, but @@ -26484,7 +26484,7 @@ namespace ts { if (!resultType) { // Types that have a reasonably good chance of being a valid operand type. - // If both types have an awaited type of one of these, we’ll assume the user + // If both types have an awaited type of one of these, we'll assume the user // might be missing an await without doing an exhaustive check that inserting // await(s) will actually be a completely valid binary expression. const closeEnoughKind = TypeFlags.NumberLike | TypeFlags.BigIntLike | TypeFlags.StringLike | TypeFlags.AnyOrUnknown; From 7c9ef505b50bc791a83db07d5814fbaf4d6fc246 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Thu, 26 Sep 2019 20:42:12 -0700 Subject: [PATCH 12/19] Fix lint, PR feedback --- src/compiler/binder.ts | 42 ++++++++++++++++++++++++++--------------- src/compiler/checker.ts | 2 +- src/compiler/debug.ts | 12 ++++++------ 3 files changed, 34 insertions(+), 22 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 7c8817fca3d33..7c5cbe0a68118 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -940,20 +940,39 @@ namespace ts { } } - function findAntecedent(flowNode: FlowNode, flags: FlowFlags) { + function excludeAntecedents(flowNode: FlowNode, flags: FlowFlags) { if (flowNode.flags & flags) { - return flowNode; + return unreachableFlow; } if (flowNode.flags & FlowFlags.BranchLabel) { const branch = flowNode as FlowLabel; if (branch.antecedents) { - for (const antecedent of branch.antecedents) { + let antecedents: FlowNode[] | undefined; + for (let i = 0; i < branch.antecedents.length; i++) { + const antecedent = branch.antecedents[i]; if (antecedent.flags & flags) { - return antecedent; + if (!antecedents) { + antecedents = branch.antecedents.slice(0, i); + } + } + else if (antecedents) { + antecedents.push(antecedent); + } + } + if (antecedents) { + if (antecedents.length === 0) { + return unreachableFlow; } + if (antecedents.length === 1) { + return antecedents[0]; + } + const newBranch = createBranchLabel(); + newBranch.antecedents = antecedents; + return newBranch; } } } + return flowNode; } function createFlowCondition(flags: FlowFlags, antecedent: FlowNode, expression: Expression | undefined): FlowNode { @@ -967,15 +986,10 @@ namespace ts { expression.kind === SyntaxKind.FalseKeyword && flags & FlowFlags.TrueCondition) { return unreachableFlow; } - if (flags & FlowFlags.TrueCondition) { - // If the antecedent is an optional chain, only the Present branch can exist on the `true` condition - const presentFlow = findAntecedent(currentFlow, FlowFlags.Present); - if (presentFlow) { - antecedent = presentFlow; - } + // A "Missing" flow node (representing the `null` or `undefined` branch of an optional chain), can never be `true`. + antecedent = excludeAntecedents(currentFlow, FlowFlags.Missing); } - if (!isNarrowingExpression(expression)) { return antecedent; } @@ -988,10 +1002,8 @@ namespace ts { return antecedent; } if (flags & FlowFlags.Present) { - const presentFlow = findAntecedent(currentFlow, FlowFlags.Present); - if (presentFlow) { - antecedent = presentFlow; - } + // A "Missing" flow node (representing the `null` or `undefined` branch of an optional chain), can never be "Present". + antecedent = excludeAntecedents(currentFlow, FlowFlags.Missing); } setFlowNodeReferenced(antecedent); return flowNodeCreated({ flags, antecedent, node: expression }); diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index fef449dc610c8..e0eda2a555ef5 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2664,7 +2664,7 @@ namespace ts { if (patternAmbientModules) { const pattern = findBestPatternMatch(patternAmbientModules, _ => _.pattern, moduleReference); if (pattern) { - // If the module reference matched a pattern ambient module ('*.foo') but there’s also a + // If the module reference matched a pattern ambient module ('*.foo') but there's also a // module augmentation by the specific name requested ('a.foo'), we store the merged symbol // by the augmentation name ('a.foo'), because asking for *.foo should not give you exports // from a.foo. diff --git a/src/compiler/debug.ts b/src/compiler/debug.ts index bdcd3d0369ca6..7cd9e70511efe 100644 --- a/src/compiler/debug.ts +++ b/src/compiler/debug.ts @@ -309,8 +309,8 @@ namespace ts { const writer = createTextWriter("\n"); writeFlowNode(flowNode, /*isAntecedent*/ false); + let hasWrittenHeader = false; while (deferredFlowNodes.length) { - let hasWrittenHeader = false; const deferred = deferredFlowNodes.splice(0, deferredFlowNodes.length); for (const sharedNode of deferred) { if (!hasWrittenHeader) { @@ -332,7 +332,7 @@ namespace ts { const flowId = `${getFlowId(flowNode)}`; const isShared = sharedNodes.get(flowId); if (isShared === undefined) { - sharedNodes.set(flowId, false) + sharedNodes.set(flowId, false); } else if (isShared === false) { sharedNodes.set(flowId, true); @@ -393,7 +393,7 @@ namespace ts { if (flags & FlowFlags.Present) return "Present"; if (flags & FlowFlags.Missing) return "Missing"; if (flags & FlowFlags.Unreachable) return "Unreachable"; - return Debug.fail(); + return fail(); } function writeHeader(flowNode: FlowNode, isAntecedent: boolean) { @@ -424,13 +424,13 @@ namespace ts { } } if (!(flowNode.flags & FlowFlags.Referenced)) { - attributes.push("unreferenced") + attributes.push("unreferenced"); } if (flowNode.flags & FlowFlags.Cached) { attributes.push("cached"); } if (some(attributes)) { - writer.write(`[${attributes.join(', ')}]`); + writer.write(`[${attributes.join(", ")}]`); } } writer.writeLine(); @@ -489,7 +489,7 @@ namespace ts { writeOptionalChain(flowNode as FlowOptionalChain); } else { - Debug.assert(!!(flowNode.flags & FlowFlags.Unreachable)); + assert(!!(flowNode.flags & FlowFlags.Unreachable)); } } From ad7c33c36dda4fd8f28259ae2744e21dbb732582 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Fri, 27 Sep 2019 20:57:16 -0700 Subject: [PATCH 13/19] Updates to control flow --- src/compiler/binder.ts | 140 ++++++++---------- src/compiler/debug.ts | 3 + src/compiler/utilities.ts | 2 +- .../controlFlowOptionalChain.errors.txt | 31 ++-- .../reference/controlFlowOptionalChain.js | 52 +++---- .../controlFlowOptionalChain.symbols | 38 ++--- .../reference/controlFlowOptionalChain.types | 88 +++++------ .../controlFlow/controlFlowOptionalChain.ts | 26 ++-- 8 files changed, 182 insertions(+), 198 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 7c5cbe0a68118..38f149c883939 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -193,8 +193,6 @@ namespace ts { let currentReturnTarget: FlowLabel | undefined; let currentTrueTarget: FlowLabel | undefined; let currentFalseTarget: FlowLabel | undefined; - let currentPresentTarget: FlowLabel | undefined; - let currentMissingTarget: FlowLabel | undefined; let preSwitchCaseFlow: FlowNode | undefined; let activeLabels: ActiveLabel[] | undefined; let hasExplicitReturn: boolean; @@ -263,8 +261,6 @@ namespace ts { currentReturnTarget = undefined; currentTrueTarget = undefined; currentFalseTarget = undefined; - currentPresentTarget = undefined; - currentMissingTarget = undefined; activeLabels = undefined!; hasExplicitReturn = false; emitFlags = NodeFlags.None; @@ -940,41 +936,6 @@ namespace ts { } } - function excludeAntecedents(flowNode: FlowNode, flags: FlowFlags) { - if (flowNode.flags & flags) { - return unreachableFlow; - } - if (flowNode.flags & FlowFlags.BranchLabel) { - const branch = flowNode as FlowLabel; - if (branch.antecedents) { - let antecedents: FlowNode[] | undefined; - for (let i = 0; i < branch.antecedents.length; i++) { - const antecedent = branch.antecedents[i]; - if (antecedent.flags & flags) { - if (!antecedents) { - antecedents = branch.antecedents.slice(0, i); - } - } - else if (antecedents) { - antecedents.push(antecedent); - } - } - if (antecedents) { - if (antecedents.length === 0) { - return unreachableFlow; - } - if (antecedents.length === 1) { - return antecedents[0]; - } - const newBranch = createBranchLabel(); - newBranch.antecedents = antecedents; - return newBranch; - } - } - } - return flowNode; - } - function createFlowCondition(flags: FlowFlags, antecedent: FlowNode, expression: Expression | undefined): FlowNode { if (antecedent.flags & FlowFlags.Unreachable) { return antecedent; @@ -986,10 +947,6 @@ namespace ts { expression.kind === SyntaxKind.FalseKeyword && flags & FlowFlags.TrueCondition) { return unreachableFlow; } - if (flags & FlowFlags.TrueCondition) { - // A "Missing" flow node (representing the `null` or `undefined` branch of an optional chain), can never be `true`. - antecedent = excludeAntecedents(currentFlow, FlowFlags.Missing); - } if (!isNarrowingExpression(expression)) { return antecedent; } @@ -1001,9 +958,8 @@ namespace ts { if (antecedent.flags & FlowFlags.Unreachable) { return antecedent; } - if (flags & FlowFlags.Present) { - // A "Missing" flow node (representing the `null` or `undefined` branch of an optional chain), can never be "Present". - antecedent = excludeAntecedents(currentFlow, FlowFlags.Missing); + if (!isNarrowingExpression(expression)) { + return antecedent; } setFlowNodeReferenced(antecedent); return flowNodeCreated({ flags, antecedent, node: expression }); @@ -1074,22 +1030,27 @@ namespace ts { } function isTopLevelLogicalExpression(node: Node): boolean { - while (node.parent.kind === SyntaxKind.ParenthesizedExpression || - node.parent.kind === SyntaxKind.PrefixUnaryExpression && - (node.parent).operator === SyntaxKind.ExclamationToken) { + while (isParenthesizedExpression(node.parent) || + isPrefixUnaryExpression(node.parent) && node.parent.operator === SyntaxKind.ExclamationToken) { node = node.parent; } - return !isStatementCondition(node) && !isLogicalExpression(node.parent); + return !isStatementCondition(node) && + !isLogicalExpression(node.parent) && + !(isOptionalChain(node.parent) && node.parent.expression === node); } - function bindCondition(node: Expression | undefined, trueTarget: FlowLabel, falseTarget: FlowLabel) { - const saveTrueTarget = currentTrueTarget; - const saveFalseTarget = currentFalseTarget; + function doWithConditionalBranches(action: (value: T) => void, value: T, trueTarget: FlowLabel, falseTarget: FlowLabel) { + const savedTrueTarget = currentTrueTarget; + const savedFalseTarget = currentFalseTarget; currentTrueTarget = trueTarget; currentFalseTarget = falseTarget; - bind(node); - currentTrueTarget = saveTrueTarget; - currentFalseTarget = saveFalseTarget; + action(value); + currentTrueTarget = savedTrueTarget; + currentFalseTarget = savedFalseTarget; + } + + function bindCondition(node: Expression | undefined, trueTarget: FlowLabel, falseTarget: FlowLabel) { + doWithConditionalBranches(bind, node, trueTarget, falseTarget); if (!node || !isLogicalExpression(node)) { addAntecedent(trueTarget, createFlowCondition(FlowFlags.TrueCondition, currentFlow, node)); addAntecedent(falseTarget, createFlowCondition(FlowFlags.FalseCondition, currentFlow, node)); @@ -1601,32 +1562,19 @@ namespace ts { } } - function bindOptionalExpression(node: Expression, presentTarget: FlowLabel, missingTarget: FlowLabel) { - const savedPresentTarget = currentPresentTarget; - const savedMissingTarget = currentMissingTarget; - currentPresentTarget = presentTarget; - currentMissingTarget = missingTarget; - bind(node); - currentPresentTarget = savedPresentTarget; - currentMissingTarget = savedMissingTarget; - if (!isValidOptionalChain(node) || isOutermostOptionalChain(node)) { - addAntecedent(presentTarget, createFlowOptionalChain(FlowFlags.Present, currentFlow, node)); - addAntecedent(missingTarget, createFlowOptionalChain(FlowFlags.Missing, currentFlow, node)); - } + function isOutermostOptionalChain(node: OptionalChain) { + return !isOptionalChain(node.parent) || isOptionalChainRoot(node.parent) || node !== node.parent.expression; } - function isOutermostOptionalChain(node: ValidOptionalChain) { - return !isOptionalChain(node.parent) || !!node.parent.questionDotToken || node.parent.expression !== node; + function bindOptionalExpression(node: Expression, trueTarget: FlowLabel, falseTarget: FlowLabel) { + doWithConditionalBranches(bind, node, trueTarget, falseTarget); + if (!isOptionalChain(node) || isOutermostOptionalChain(node)) { + addAntecedent(trueTarget, createFlowOptionalChain(FlowFlags.Present, currentFlow, node)); + addAntecedent(falseTarget, createFlowOptionalChain(FlowFlags.Missing, currentFlow, node)); + } } - function bindOptionalChainFlow(node: ValidOptionalChain) { - const postExpressionLabel = isOutermostOptionalChain(node) ? createBranchLabel() : undefined; - const presentTarget = postExpressionLabel ? createBranchLabel() : Debug.assertDefined(currentPresentTarget); - const missingTarget = postExpressionLabel ? createBranchLabel() : Debug.assertDefined(currentMissingTarget); - bindOptionalExpression(node.expression, presentTarget, missingTarget); - if (!isValidOptionalChain(node.expression) || node.questionDotToken) { - currentFlow = finishFlowLabel(presentTarget); - } + function bindOptionalChainRest(node: OptionalChain) { bind(node.questionDotToken); switch (node.kind) { case SyntaxKind.PropertyAccessExpression: @@ -1640,11 +1588,41 @@ namespace ts { bindEach(node.arguments); break; } - if (postExpressionLabel) { - addAntecedent(postExpressionLabel, currentFlow); - addAntecedent(postExpressionLabel, finishFlowLabel(missingTarget)); + } + + function bindOptionalChain(node: ValidOptionalChain, trueTarget: FlowLabel, falseTarget: FlowLabel) { + // For an optional chain, we emulate the behavior of a logical expression: + // + // a?.b -> a && a.b + // a?.b.c -> a && a.b.c + // a?.b?.c -> a && a.b && a.b.c + // a?.[x = 1] -> a && a[x = 1] + // + // To do this we descend through the chain until we reach the root of a chain (the expression with a `?.`) + // and build it's CFA graph as if it were the first condition (`a && ...`). Then we bind the rest + // of the node as part of the "true" branch, and continue to do so as we ascend back up to the outermost + // chain node. We then treat the entire node as the right side of the expression. + const preChainLabel = node.questionDotToken ? createBranchLabel() : undefined; + bindOptionalExpression(node.expression, preChainLabel || trueTarget, falseTarget); + if (preChainLabel) { + currentFlow = finishFlowLabel(preChainLabel); + } + doWithConditionalBranches(bindOptionalChainRest, node, trueTarget, falseTarget); + if (isOutermostOptionalChain(node)) { + addAntecedent(trueTarget, createFlowOptionalChain(FlowFlags.Present, currentFlow, node)); + addAntecedent(falseTarget, createFlowOptionalChain(FlowFlags.Missing, currentFlow, node)); + } + } + + function bindOptionalChainFlow(node: ValidOptionalChain) { + if (isTopLevelLogicalExpression(node)) { + const postExpressionLabel = createBranchLabel(); + bindOptionalChain(node, postExpressionLabel, postExpressionLabel); currentFlow = finishFlowLabel(postExpressionLabel); } + else { + bindOptionalChain(node, currentTrueTarget!, currentFalseTarget!); + } } function bindAccessExpressionFlow(node: AccessExpression) { diff --git a/src/compiler/debug.ts b/src/compiler/debug.ts index 7cd9e70511efe..9d7c42ae1d039 100644 --- a/src/compiler/debug.ts +++ b/src/compiler/debug.ts @@ -328,6 +328,9 @@ namespace ts { return writer.getText(); function trackSharedNodesInGraph(flowNode: FlowNode) { + if (flowNode.flags & FlowFlags.Start) { + return; + } if (flowNode.flags & FlowFlags.Shared) { const flowId = `${getFlowId(flowNode)}`; const isShared = sharedNodes.get(flowId); diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index b0d25639b1717..823c29c04df89 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -7139,7 +7139,7 @@ namespace ts { } /* @internal */ - export function isOptionalChainRoot(node: ValidOptionalChain): node is OptionalChainRoot { + export function isOptionalChainRoot(node: OptionalChain): node is OptionalChainRoot { return !!node.questionDotToken; } diff --git a/tests/baselines/reference/controlFlowOptionalChain.errors.txt b/tests/baselines/reference/controlFlowOptionalChain.errors.txt index 2b7588429d465..aef3bc602433a 100644 --- a/tests/baselines/reference/controlFlowOptionalChain.errors.txt +++ b/tests/baselines/reference/controlFlowOptionalChain.errors.txt @@ -6,6 +6,7 @@ tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(35,5): error TS2 tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(39,1): error TS2722: Cannot invoke an object which is possibly 'undefined'. tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(52,5): error TS2532: Object is possibly 'undefined'. tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(57,1): error TS2532: Object is possibly 'undefined'. +tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(62,5): error TS2532: Object is possibly 'undefined'. tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(68,5): error TS2532: Object is possibly 'undefined'. tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(72,1): error TS2532: Object is possibly 'undefined'. tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(83,5): error TS2532: Object is possibly 'undefined'. @@ -20,7 +21,7 @@ tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(130,5): error TS tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(134,1): error TS2532: Object is possibly 'undefined'. -==== tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts (20 errors) ==== +==== tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts (21 errors) ==== // assignments in shortcutting chain declare const o: undefined | { [key: string]: any; @@ -56,7 +57,7 @@ tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(134,1): error TS declare const f: undefined | ((x: any) => x is number); declare const x: string | number; if (f?.(x)) { - x; + x; // TODO: should be `number` f; f(x); } @@ -75,7 +76,7 @@ tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(134,1): error TS declare const o2: { f(x: any): x is number; } | undefined; if (o2?.f(x)) { - x; + x; // TODO: should be `number` o2.f; o2?.f; o2?.f(x); @@ -97,8 +98,10 @@ tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(134,1): error TS declare const o3: { x: 1, y: string } | { x: 2, y: number } | undefined; if (o3?.x === 1) { - o3; - o3.x; + o3; // TODO: should be `{ x: y, y: string }` + o3.x; // TODO: should not be an error. + ~~ +!!! error TS2532: Object is possibly 'undefined'. o3?.x; } else { @@ -117,8 +120,8 @@ tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(134,1): error TS declare const o4: { x?: { y: boolean } }; if (o4.x?.y) { o4.x; - o4.x?.y; - o4.x.y; + o4.x.y; // TODO: should be `true` + o4.x?.y; // TODO: should be `true` } else { o4.x; @@ -138,20 +141,20 @@ tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(134,1): error TS o5.x; o5.x.y; o5.x.y.z; - o5.x.y.z.w; - o5.x.y.z?.w; - o5.x?.y.z.w; - o5.x?.y.z?.w; + o5.x.y.z.w; // TODO: should be `true` + o5.x.y.z?.w; // TODO: should be `true` + o5.x?.y.z.w; // TODO: should be `true` + o5.x?.y.z?.w; // TODO: should be `true` } else { o5.x; o5.x?.y; o5.x?.y.z; - o5.x?.y.z?.w; + o5.x?.y.z?.w; // TODO: should be `false | undefined` o5.x.y; ~~~~ !!! error TS2532: Object is possibly 'undefined'. - o5.x.y.z.w; + o5.x.y.z.w; // TODO: should be `false | undefined` ~~~~ !!! error TS2532: Object is possibly 'undefined'. ~~~~~~~~ @@ -180,7 +183,7 @@ tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(134,1): error TS declare const o6: Base | undefined; if (o6?.f()) { - o6; + o6; // TODO: should be `Derived` o6.f; } else { diff --git a/tests/baselines/reference/controlFlowOptionalChain.js b/tests/baselines/reference/controlFlowOptionalChain.js index 9ca865e711f96..77b0803de0381 100644 --- a/tests/baselines/reference/controlFlowOptionalChain.js +++ b/tests/baselines/reference/controlFlowOptionalChain.js @@ -26,7 +26,7 @@ d.toString(); declare const f: undefined | ((x: any) => x is number); declare const x: string | number; if (f?.(x)) { - x; + x; // TODO: should be `number` f; f(x); } @@ -41,7 +41,7 @@ f(x); declare const o2: { f(x: any): x is number; } | undefined; if (o2?.f(x)) { - x; + x; // TODO: should be `number` o2.f; o2?.f; o2?.f(x); @@ -59,8 +59,8 @@ o2.f; declare const o3: { x: 1, y: string } | { x: 2, y: number } | undefined; if (o3?.x === 1) { - o3; - o3.x; + o3; // TODO: should be `{ x: y, y: string }` + o3.x; // TODO: should not be an error. o3?.x; } else { @@ -75,8 +75,8 @@ o3.x; declare const o4: { x?: { y: boolean } }; if (o4.x?.y) { o4.x; - o4.x?.y; - o4.x.y; + o4.x.y; // TODO: should be `true` + o4.x?.y; // TODO: should be `true` } else { o4.x; @@ -92,18 +92,18 @@ if (o5.x?.y.z?.w) { o5.x; o5.x.y; o5.x.y.z; - o5.x.y.z.w; - o5.x.y.z?.w; - o5.x?.y.z.w; - o5.x?.y.z?.w; + o5.x.y.z.w; // TODO: should be `true` + o5.x.y.z?.w; // TODO: should be `true` + o5.x?.y.z.w; // TODO: should be `true` + o5.x?.y.z?.w; // TODO: should be `true` } else { o5.x; o5.x?.y; o5.x?.y.z; - o5.x?.y.z?.w; + o5.x?.y.z?.w; // TODO: should be `false | undefined` o5.x.y; - o5.x.y.z.w; + o5.x.y.z.w; // TODO: should be `false | undefined` } o5.x; o5.x?.y; @@ -122,7 +122,7 @@ interface Derived extends Base { declare const o6: Base | undefined; if (o6?.f()) { - o6; + o6; // TODO: should be `Derived` o6.f; } else { @@ -177,7 +177,7 @@ var d; (_d = o) === null || _d === void 0 ? void 0 : _d.x(d = 1); d.toString(); if ((_e = f) === null || _e === void 0 ? void 0 : _e(x)) { - x; + x; // TODO: should be `number` f; f(x); } @@ -190,7 +190,7 @@ x; f; f(x); if ((_f = o2) === null || _f === void 0 ? void 0 : _f.f(x)) { - x; + x; // TODO: should be `number` o2.f; (_g = o2) === null || _g === void 0 ? void 0 : _g.f; (_h = o2) === null || _h === void 0 ? void 0 : _h.f(x); @@ -206,8 +206,8 @@ o2; (_k = o2) === null || _k === void 0 ? void 0 : _k.f; o2.f; if (((_l = o3) === null || _l === void 0 ? void 0 : _l.x) === 1) { - o3; - o3.x; + o3; // TODO: should be `{ x: y, y: string }` + o3.x; // TODO: should not be an error. (_m = o3) === null || _m === void 0 ? void 0 : _m.x; } else { @@ -220,8 +220,8 @@ o3; o3.x; if ((_q = o4.x) === null || _q === void 0 ? void 0 : _q.y) { o4.x; - (_r = o4.x) === null || _r === void 0 ? void 0 : _r.y; - o4.x.y; + o4.x.y; // TODO: should be `true` + (_r = o4.x) === null || _r === void 0 ? void 0 : _r.y; // TODO: should be `true` } else { o4.x; @@ -235,18 +235,18 @@ if ((_v = (_u = o5.x) === null || _u === void 0 ? void 0 : _u.y.z) === null || _ o5.x; o5.x.y; o5.x.y.z; - o5.x.y.z.w; - (_w = o5.x.y.z) === null || _w === void 0 ? void 0 : _w.w; - (_x = o5.x) === null || _x === void 0 ? void 0 : _x.y.z.w; - (_z = (_y = o5.x) === null || _y === void 0 ? void 0 : _y.y.z) === null || _z === void 0 ? void 0 : _z.w; + o5.x.y.z.w; // TODO: should be `true` + (_w = o5.x.y.z) === null || _w === void 0 ? void 0 : _w.w; // TODO: should be `true` + (_x = o5.x) === null || _x === void 0 ? void 0 : _x.y.z.w; // TODO: should be `true` + (_z = (_y = o5.x) === null || _y === void 0 ? void 0 : _y.y.z) === null || _z === void 0 ? void 0 : _z.w; // TODO: should be `true` } else { o5.x; (_0 = o5.x) === null || _0 === void 0 ? void 0 : _0.y; (_1 = o5.x) === null || _1 === void 0 ? void 0 : _1.y.z; - (_3 = (_2 = o5.x) === null || _2 === void 0 ? void 0 : _2.y.z) === null || _3 === void 0 ? void 0 : _3.w; + (_3 = (_2 = o5.x) === null || _2 === void 0 ? void 0 : _2.y.z) === null || _3 === void 0 ? void 0 : _3.w; // TODO: should be `false | undefined` o5.x.y; - o5.x.y.z.w; + o5.x.y.z.w; // TODO: should be `false | undefined` } o5.x; (_4 = o5.x) === null || _4 === void 0 ? void 0 : _4.y; @@ -255,7 +255,7 @@ o5.x; o5.x.y; o5.x.y.z.w; if ((_8 = o6) === null || _8 === void 0 ? void 0 : _8.f()) { - o6; + o6; // TODO: should be `Derived` o6.f; } else { diff --git a/tests/baselines/reference/controlFlowOptionalChain.symbols b/tests/baselines/reference/controlFlowOptionalChain.symbols index 92212539c4d6d..29651e88f4093 100644 --- a/tests/baselines/reference/controlFlowOptionalChain.symbols +++ b/tests/baselines/reference/controlFlowOptionalChain.symbols @@ -75,7 +75,7 @@ if (f?.(x)) { >f : Symbol(f, Decl(controlFlowOptionalChain.ts, 24, 13)) >x : Symbol(x, Decl(controlFlowOptionalChain.ts, 25, 13)) - x; + x; // TODO: should be `number` >x : Symbol(x, Decl(controlFlowOptionalChain.ts, 25, 13)) f; @@ -118,7 +118,7 @@ if (o2?.f(x)) { >f : Symbol(f, Decl(controlFlowOptionalChain.ts, 40, 19)) >x : Symbol(x, Decl(controlFlowOptionalChain.ts, 25, 13)) - x; + x; // TODO: should be `number` >x : Symbol(x, Decl(controlFlowOptionalChain.ts, 25, 13)) o2.f; @@ -182,18 +182,18 @@ if (o3?.x === 1) { >o3 : Symbol(o3, Decl(controlFlowOptionalChain.ts, 58, 13)) >x : Symbol(x, Decl(controlFlowOptionalChain.ts, 58, 19), Decl(controlFlowOptionalChain.ts, 58, 41)) - o3; + o3; // TODO: should be `{ x: y, y: string }` >o3 : Symbol(o3, Decl(controlFlowOptionalChain.ts, 58, 13)) - o3.x; ->o3.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 58, 19)) + o3.x; // TODO: should not be an error. +>o3.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 58, 19), Decl(controlFlowOptionalChain.ts, 58, 41)) >o3 : Symbol(o3, Decl(controlFlowOptionalChain.ts, 58, 13)) ->x : Symbol(x, Decl(controlFlowOptionalChain.ts, 58, 19)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 58, 19), Decl(controlFlowOptionalChain.ts, 58, 41)) o3?.x; ->o3?.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 58, 19)) +>o3?.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 58, 19), Decl(controlFlowOptionalChain.ts, 58, 41)) >o3 : Symbol(o3, Decl(controlFlowOptionalChain.ts, 58, 13)) ->x : Symbol(x, Decl(controlFlowOptionalChain.ts, 58, 19)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 58, 19), Decl(controlFlowOptionalChain.ts, 58, 41)) } else { o3; @@ -239,15 +239,15 @@ if (o4.x?.y) { >o4 : Symbol(o4, Decl(controlFlowOptionalChain.ts, 73, 13)) >x : Symbol(x, Decl(controlFlowOptionalChain.ts, 73, 19)) - o4.x?.y; ->o4.x?.y : Symbol(y, Decl(controlFlowOptionalChain.ts, 73, 25)) + o4.x.y; // TODO: should be `true` +>o4.x.y : Symbol(y, Decl(controlFlowOptionalChain.ts, 73, 25)) >o4.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 73, 19)) >o4 : Symbol(o4, Decl(controlFlowOptionalChain.ts, 73, 13)) >x : Symbol(x, Decl(controlFlowOptionalChain.ts, 73, 19)) >y : Symbol(y, Decl(controlFlowOptionalChain.ts, 73, 25)) - o4.x.y; ->o4.x.y : Symbol(y, Decl(controlFlowOptionalChain.ts, 73, 25)) + o4.x?.y; // TODO: should be `true` +>o4.x?.y : Symbol(y, Decl(controlFlowOptionalChain.ts, 73, 25)) >o4.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 73, 19)) >o4 : Symbol(o4, Decl(controlFlowOptionalChain.ts, 73, 13)) >x : Symbol(x, Decl(controlFlowOptionalChain.ts, 73, 19)) @@ -331,7 +331,7 @@ if (o5.x?.y.z?.w) { >y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) >z : Symbol(z, Decl(controlFlowOptionalChain.ts, 88, 30)) - o5.x.y.z.w; + o5.x.y.z.w; // TODO: should be `true` >o5.x.y.z.w : Symbol(w, Decl(controlFlowOptionalChain.ts, 88, 36)) >o5.x.y.z : Symbol(z, Decl(controlFlowOptionalChain.ts, 88, 30)) >o5.x.y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) @@ -342,7 +342,7 @@ if (o5.x?.y.z?.w) { >z : Symbol(z, Decl(controlFlowOptionalChain.ts, 88, 30)) >w : Symbol(w, Decl(controlFlowOptionalChain.ts, 88, 36)) - o5.x.y.z?.w; + o5.x.y.z?.w; // TODO: should be `true` >o5.x.y.z?.w : Symbol(w, Decl(controlFlowOptionalChain.ts, 88, 36)) >o5.x.y.z : Symbol(z, Decl(controlFlowOptionalChain.ts, 88, 30)) >o5.x.y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) @@ -353,7 +353,7 @@ if (o5.x?.y.z?.w) { >z : Symbol(z, Decl(controlFlowOptionalChain.ts, 88, 30)) >w : Symbol(w, Decl(controlFlowOptionalChain.ts, 88, 36)) - o5.x?.y.z.w; + o5.x?.y.z.w; // TODO: should be `true` >o5.x?.y.z.w : Symbol(w, Decl(controlFlowOptionalChain.ts, 88, 36)) >o5.x?.y.z : Symbol(z, Decl(controlFlowOptionalChain.ts, 88, 30)) >o5.x?.y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) @@ -364,7 +364,7 @@ if (o5.x?.y.z?.w) { >z : Symbol(z, Decl(controlFlowOptionalChain.ts, 88, 30)) >w : Symbol(w, Decl(controlFlowOptionalChain.ts, 88, 36)) - o5.x?.y.z?.w; + o5.x?.y.z?.w; // TODO: should be `true` >o5.x?.y.z?.w : Symbol(w, Decl(controlFlowOptionalChain.ts, 88, 36)) >o5.x?.y.z : Symbol(z, Decl(controlFlowOptionalChain.ts, 88, 30)) >o5.x?.y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) @@ -397,7 +397,7 @@ else { >y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) >z : Symbol(z, Decl(controlFlowOptionalChain.ts, 88, 30)) - o5.x?.y.z?.w; + o5.x?.y.z?.w; // TODO: should be `false | undefined` >o5.x?.y.z?.w : Symbol(w, Decl(controlFlowOptionalChain.ts, 88, 36)) >o5.x?.y.z : Symbol(z, Decl(controlFlowOptionalChain.ts, 88, 30)) >o5.x?.y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) @@ -415,7 +415,7 @@ else { >x : Symbol(x, Decl(controlFlowOptionalChain.ts, 88, 19)) >y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) - o5.x.y.z.w; + o5.x.y.z.w; // TODO: should be `false | undefined` >o5.x.y.z.w : Symbol(w, Decl(controlFlowOptionalChain.ts, 88, 36)) >o5.x.y.z : Symbol(z, Decl(controlFlowOptionalChain.ts, 88, 30)) >o5.x.y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) @@ -501,7 +501,7 @@ if (o6?.f()) { >o6 : Symbol(o6, Decl(controlFlowOptionalChain.ts, 121, 13)) >f : Symbol(Base.f, Decl(controlFlowOptionalChain.ts, 113, 16)) - o6; + o6; // TODO: should be `Derived` >o6 : Symbol(o6, Decl(controlFlowOptionalChain.ts, 121, 13)) o6.f; diff --git a/tests/baselines/reference/controlFlowOptionalChain.types b/tests/baselines/reference/controlFlowOptionalChain.types index c65e8e27fabec..bddbb3d207017 100644 --- a/tests/baselines/reference/controlFlowOptionalChain.types +++ b/tests/baselines/reference/controlFlowOptionalChain.types @@ -95,8 +95,8 @@ if (f?.(x)) { >f : ((x: any) => x is number) | undefined >x : string | number - x; ->x : number + x; // TODO: should be `number` +>x : string | number f; >f : (x: any) => x is number @@ -104,7 +104,7 @@ if (f?.(x)) { f(x); >f(x) : boolean >f : (x: any) => x is number ->x : number +>x : string | number } else { x; @@ -141,8 +141,8 @@ if (o2?.f(x)) { >f : ((x: any) => x is number) | undefined >x : string | number - x; ->x : number + x; // TODO: should be `number` +>x : string | number o2.f; >o2.f : (x: any) => x is number @@ -159,7 +159,7 @@ if (o2?.f(x)) { >o2?.f : (x: any) => x is number >o2 : { f(x: any): x is number; } >f : (x: any) => x is number ->x : number +>x : string | number } else { x; @@ -208,18 +208,18 @@ if (o3?.x === 1) { >x : 1 | 2 | undefined >1 : 1 - o3; ->o3 : { x: 1; y: string; } + o3; // TODO: should be `{ x: y, y: string }` +>o3 : { x: 1; y: string; } | { x: 2; y: number; } | undefined - o3.x; + o3.x; // TODO: should not be an error. >o3.x : 1 ->o3 : { x: 1; y: string; } +>o3 : { x: 1; y: string; } | { x: 2; y: number; } | undefined >x : 1 o3?.x; ->o3?.x : 1 ->o3 : { x: 1; y: string; } ->x : 1 +>o3?.x : 1 | undefined +>o3 : { x: 1; y: string; } | { x: 2; y: number; } | undefined +>x : 1 | undefined } else { o3; @@ -265,19 +265,19 @@ if (o4.x?.y) { >o4 : { x?: { y: boolean; } | undefined; } >x : { y: boolean; } - o4.x?.y; ->o4.x?.y : true + o4.x.y; // TODO: should be `true` +>o4.x.y : boolean >o4.x : { y: boolean; } >o4 : { x?: { y: boolean; } | undefined; } >x : { y: boolean; } ->y : true +>y : boolean - o4.x.y; ->o4.x.y : true + o4.x?.y; // TODO: should be `true` +>o4.x?.y : boolean >o4.x : { y: boolean; } >o4 : { x?: { y: boolean; } | undefined; } >x : { y: boolean; } ->y : true +>y : boolean } else { o4.x; @@ -286,18 +286,18 @@ else { >x : { y: boolean; } | undefined o4.x?.y; ->o4.x?.y : false | undefined +>o4.x?.y : boolean | undefined >o4.x : { y: boolean; } | undefined >o4 : { x?: { y: boolean; } | undefined; } >x : { y: boolean; } | undefined ->y : false | undefined +>y : boolean | undefined o4.x.y; ->o4.x.y : false +>o4.x.y : boolean >o4.x : { y: boolean; } | undefined >o4 : { x?: { y: boolean; } | undefined; } >x : { y: boolean; } | undefined ->y : false +>y : boolean } o4.x; >o4.x : { y: boolean; } | undefined @@ -357,8 +357,8 @@ if (o5.x?.y.z?.w) { >y : { z?: { w: boolean; } | undefined; } >z : { w: boolean; } - o5.x.y.z.w; ->o5.x.y.z.w : true + o5.x.y.z.w; // TODO: should be `true` +>o5.x.y.z.w : boolean >o5.x.y.z : { w: boolean; } >o5.x.y : { z?: { w: boolean; } | undefined; } >o5.x : { y: { z?: { w: boolean; } | undefined; }; } @@ -366,10 +366,10 @@ if (o5.x?.y.z?.w) { >x : { y: { z?: { w: boolean; } | undefined; }; } >y : { z?: { w: boolean; } | undefined; } >z : { w: boolean; } ->w : true +>w : boolean - o5.x.y.z?.w; ->o5.x.y.z?.w : true + o5.x.y.z?.w; // TODO: should be `true` +>o5.x.y.z?.w : boolean >o5.x.y.z : { w: boolean; } >o5.x.y : { z?: { w: boolean; } | undefined; } >o5.x : { y: { z?: { w: boolean; } | undefined; }; } @@ -377,10 +377,10 @@ if (o5.x?.y.z?.w) { >x : { y: { z?: { w: boolean; } | undefined; }; } >y : { z?: { w: boolean; } | undefined; } >z : { w: boolean; } ->w : true +>w : boolean - o5.x?.y.z.w; ->o5.x?.y.z.w : true + o5.x?.y.z.w; // TODO: should be `true` +>o5.x?.y.z.w : boolean >o5.x?.y.z : { w: boolean; } >o5.x?.y : { z?: { w: boolean; } | undefined; } >o5.x : { y: { z?: { w: boolean; } | undefined; }; } @@ -388,10 +388,10 @@ if (o5.x?.y.z?.w) { >x : { y: { z?: { w: boolean; } | undefined; }; } >y : { z?: { w: boolean; } | undefined; } >z : { w: boolean; } ->w : true +>w : boolean - o5.x?.y.z?.w; ->o5.x?.y.z?.w : true + o5.x?.y.z?.w; // TODO: should be `true` +>o5.x?.y.z?.w : boolean >o5.x?.y.z : { w: boolean; } >o5.x?.y : { z?: { w: boolean; } | undefined; } >o5.x : { y: { z?: { w: boolean; } | undefined; }; } @@ -399,7 +399,7 @@ if (o5.x?.y.z?.w) { >x : { y: { z?: { w: boolean; } | undefined; }; } >y : { z?: { w: boolean; } | undefined; } >z : { w: boolean; } ->w : true +>w : boolean } else { o5.x; @@ -423,8 +423,8 @@ else { >y : { z?: { w: boolean; } | undefined; } | undefined >z : { w: boolean; } | undefined - o5.x?.y.z?.w; ->o5.x?.y.z?.w : false | undefined + o5.x?.y.z?.w; // TODO: should be `false | undefined` +>o5.x?.y.z?.w : boolean | undefined >o5.x?.y.z : { w: boolean; } | undefined >o5.x?.y : { z?: { w: boolean; } | undefined; } | undefined >o5.x : { y: { z?: { w: boolean; } | undefined; }; } | undefined @@ -432,7 +432,7 @@ else { >x : { y: { z?: { w: boolean; } | undefined; }; } | undefined >y : { z?: { w: boolean; } | undefined; } | undefined >z : { w: boolean; } | undefined ->w : false | undefined +>w : boolean | undefined o5.x.y; >o5.x.y : { z?: { w: boolean; } | undefined; } @@ -441,8 +441,8 @@ else { >x : { y: { z?: { w: boolean; } | undefined; }; } | undefined >y : { z?: { w: boolean; } | undefined; } - o5.x.y.z.w; ->o5.x.y.z.w : false + o5.x.y.z.w; // TODO: should be `false | undefined` +>o5.x.y.z.w : boolean >o5.x.y.z : { w: boolean; } | undefined >o5.x.y : { z?: { w: boolean; } | undefined; } >o5.x : { y: { z?: { w: boolean; } | undefined; }; } | undefined @@ -450,7 +450,7 @@ else { >x : { y: { z?: { w: boolean; } | undefined; }; } | undefined >y : { z?: { w: boolean; } | undefined; } >z : { w: boolean; } | undefined ->w : false +>w : boolean } o5.x; >o5.x : { y: { z?: { w: boolean; } | undefined; }; } | undefined @@ -521,12 +521,12 @@ if (o6?.f()) { >o6 : Base | undefined >f : (() => this is Derived) | undefined - o6; ->o6 : Derived + o6; // TODO: should be `Derived` +>o6 : Base o6.f; >o6.f : () => this is Derived ->o6 : Derived +>o6 : Base >f : () => this is Derived } else { diff --git a/tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts b/tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts index 9cfed0fcb959f..2e07ad062c1bf 100644 --- a/tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts +++ b/tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts @@ -28,7 +28,7 @@ d.toString(); declare const f: undefined | ((x: any) => x is number); declare const x: string | number; if (f?.(x)) { - x; + x; // TODO: should be `number` f; f(x); } @@ -43,7 +43,7 @@ f(x); declare const o2: { f(x: any): x is number; } | undefined; if (o2?.f(x)) { - x; + x; // TODO: should be `number` o2.f; o2?.f; o2?.f(x); @@ -61,8 +61,8 @@ o2.f; declare const o3: { x: 1, y: string } | { x: 2, y: number } | undefined; if (o3?.x === 1) { - o3; - o3.x; + o3; // TODO: should be `{ x: y, y: string }` + o3.x; // TODO: should not be an error. o3?.x; } else { @@ -77,8 +77,8 @@ o3.x; declare const o4: { x?: { y: boolean } }; if (o4.x?.y) { o4.x; - o4.x?.y; - o4.x.y; + o4.x.y; // TODO: should be `true` + o4.x?.y; // TODO: should be `true` } else { o4.x; @@ -94,18 +94,18 @@ if (o5.x?.y.z?.w) { o5.x; o5.x.y; o5.x.y.z; - o5.x.y.z.w; - o5.x.y.z?.w; - o5.x?.y.z.w; - o5.x?.y.z?.w; + o5.x.y.z.w; // TODO: should be `true` + o5.x.y.z?.w; // TODO: should be `true` + o5.x?.y.z.w; // TODO: should be `true` + o5.x?.y.z?.w; // TODO: should be `true` } else { o5.x; o5.x?.y; o5.x?.y.z; - o5.x?.y.z?.w; + o5.x?.y.z?.w; // TODO: should be `false | undefined` o5.x.y; - o5.x.y.z.w; + o5.x.y.z.w; // TODO: should be `false | undefined` } o5.x; o5.x?.y; @@ -124,7 +124,7 @@ interface Derived extends Base { declare const o6: Base | undefined; if (o6?.f()) { - o6; + o6; // TODO: should be `Derived` o6.f; } else { From 6b49a031c8713790eec268df8105716f50e8478c Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Sat, 28 Sep 2019 17:14:41 -0700 Subject: [PATCH 14/19] Switch to FlowCondition for CFA of optional chains --- src/compiler/binder.ts | 23 ++----- src/compiler/checker.ts | 23 ++----- src/compiler/debug.ts | 14 +--- src/compiler/types.ts | 11 +--- src/compiler/utilities.ts | 4 +- .../controlFlowOptionalChain.errors.txt | 28 ++++---- .../reference/controlFlowOptionalChain.js | 56 ++++++++-------- .../controlFlowOptionalChain.symbols | 28 ++++---- .../reference/controlFlowOptionalChain.types | 64 +++++++++---------- .../controlFlow/controlFlowOptionalChain.ts | 28 ++++---- 10 files changed, 117 insertions(+), 162 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 38f149c883939..b58c099acfa02 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -945,18 +945,9 @@ namespace ts { } if (expression.kind === SyntaxKind.TrueKeyword && flags & FlowFlags.FalseCondition || expression.kind === SyntaxKind.FalseKeyword && flags & FlowFlags.TrueCondition) { - return unreachableFlow; - } - if (!isNarrowingExpression(expression)) { - return antecedent; - } - setFlowNodeReferenced(antecedent); - return flowNodeCreated({ flags, antecedent, node: expression }); - } - - function createFlowOptionalChain(flags: FlowFlags, antecedent: FlowNode, expression: Expression): FlowNode { - if (antecedent.flags & FlowFlags.Unreachable) { - return antecedent; + if (!isOptionalChainRoot(expression.parent)) { + return unreachableFlow; + } } if (!isNarrowingExpression(expression)) { return antecedent; @@ -1569,8 +1560,8 @@ namespace ts { function bindOptionalExpression(node: Expression, trueTarget: FlowLabel, falseTarget: FlowLabel) { doWithConditionalBranches(bind, node, trueTarget, falseTarget); if (!isOptionalChain(node) || isOutermostOptionalChain(node)) { - addAntecedent(trueTarget, createFlowOptionalChain(FlowFlags.Present, currentFlow, node)); - addAntecedent(falseTarget, createFlowOptionalChain(FlowFlags.Missing, currentFlow, node)); + addAntecedent(trueTarget, createFlowCondition(FlowFlags.TrueCondition, currentFlow, node)); + addAntecedent(falseTarget, createFlowCondition(FlowFlags.FalseCondition, currentFlow, node)); } } @@ -1609,8 +1600,8 @@ namespace ts { } doWithConditionalBranches(bindOptionalChainRest, node, trueTarget, falseTarget); if (isOutermostOptionalChain(node)) { - addAntecedent(trueTarget, createFlowOptionalChain(FlowFlags.Present, currentFlow, node)); - addAntecedent(falseTarget, createFlowOptionalChain(FlowFlags.Missing, currentFlow, node)); + addAntecedent(trueTarget, createFlowCondition(FlowFlags.TrueCondition, currentFlow, node)); + addAntecedent(falseTarget, createFlowCondition(FlowFlags.FalseCondition, currentFlow, node)); } } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e0eda2a555ef5..93163277167c8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -18794,9 +18794,6 @@ namespace ts { else if (flags & FlowFlags.Condition) { type = getTypeAtFlowCondition(flow); } - else if (flags & FlowFlags.OptionalChain) { - type = getTypeAtFlowOptionalChain(flow); - } else if (flags & FlowFlags.SwitchClause) { type = getTypeAtSwitchClause(flow); } @@ -19003,22 +19000,6 @@ namespace ts { return createFlowType(resultType, incomplete); } - function getTypeAtFlowOptionalChain(flow: FlowOptionalChain): FlowType { - const flowType = getTypeAtFlowNode(flow.antecedent); - const type = getTypeFromFlowType(flowType); - if (type.flags & TypeFlags.Never) { - return flowType; - } - - const assumePresent = (flow.flags & FlowFlags.Present) !== 0; - const nonEvolvingType = finalizeEvolvingArrayType(type); - const narrowedType = narrowTypeByOptionality(nonEvolvingType, flow.node, assumePresent); - if (narrowedType === nonEvolvingType) { - return flowType; - } - return createFlowType(narrowedType, isIncomplete(flowType)); - } - function getTypeAtSwitchClause(flow: FlowSwitchClause): FlowType { const expr = flow.switchStatement.expression; const flowType = getTypeAtFlowNode(flow.antecedent); @@ -19602,6 +19583,10 @@ namespace ts { // Narrow the given type based on the given expression having the assumed boolean value. The returned type // 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)) { + return narrowTypeByOptionality(type, expr, assumeTrue); + } switch (expr.kind) { case SyntaxKind.Identifier: case SyntaxKind.ThisKeyword: diff --git a/src/compiler/debug.ts b/src/compiler/debug.ts index 9d7c42ae1d039..93b3992dbbaa0 100644 --- a/src/compiler/debug.ts +++ b/src/compiler/debug.ts @@ -284,8 +284,7 @@ namespace ts { FlowFlags.ArrayMutation | FlowFlags.Call | FlowFlags.PreFinally | - FlowFlags.AfterFinally | - FlowFlags.OptionalChain; + FlowFlags.AfterFinally; type hasAntecedent = | FlowAssignment @@ -295,7 +294,6 @@ namespace ts { | FlowCall | PreFinallyFlow | AfterFinallyFlow - | FlowOptionalChain ; const flowNodes: FlowNode[] = []; @@ -393,8 +391,6 @@ namespace ts { if (flags & FlowFlags.Call) return "Call"; if (flags & FlowFlags.PreFinally) return "PreFinally"; if (flags & FlowFlags.AfterFinally) return "AfterFinally"; - if (flags & FlowFlags.Present) return "Present"; - if (flags & FlowFlags.Missing) return "Missing"; if (flags & FlowFlags.Unreachable) return "Unreachable"; return fail(); } @@ -488,9 +484,6 @@ namespace ts { else if (flowNode.flags & FlowFlags.AfterFinally) { writeAfterFinally(flowNode as AfterFinallyFlow); } - else if (flowNode.flags & FlowFlags.OptionalChain) { - writeOptionalChain(flowNode as FlowOptionalChain); - } else { assert(!!(flowNode.flags & FlowFlags.Unreachable)); } @@ -538,11 +531,6 @@ namespace ts { function writeAfterFinally(flowNode: AfterFinallyFlow) { writeAntecedent(flowNode.antecedent); } - - function writeOptionalChain(flowNode: FlowOptionalChain) { - writeNode(flowNode.node); - writeAntecedent(flowNode.antecedent); - } } } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 7d17a9fde61b6..537227c1f1ef8 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2682,14 +2682,11 @@ namespace ts { Shared = 1 << 11, // Referenced as antecedent more than once PreFinally = 1 << 12, // Injected edge that links pre-finally label and pre-try flow AfterFinally = 1 << 13, // Injected edge that links post-finally flow with the rest of the graph - Present = 1 << 14, // Optional Chain known to be neither null nor undefined. - Missing = 1 << 15, // Optional Chain known to be either null or undefined. /** @internal */ - Cached = 1 << 16, // Indicates that at least one cross-call cache entry exists for this node, even if not a loop participant + Cached = 1 << 14, // Indicates that at least one cross-call cache entry exists for this node, even if not a loop participant Label = BranchLabel | LoopLabel, Condition = TrueCondition | FalseCondition, - OptionalChain = Present | Missing, } export type FlowNode = @@ -2700,7 +2697,6 @@ namespace ts { | FlowAssignment | FlowCall | FlowCondition - | FlowOptionalChain | FlowSwitchClause | FlowArrayMutation; @@ -2753,11 +2749,6 @@ namespace ts { antecedent: FlowNode; } - export interface FlowOptionalChain extends FlowNodeBase { - node: Expression; - antecedent: FlowNode; - } - export interface FlowSwitchClause extends FlowNodeBase { switchStatement: SwitchStatement; clauseStart: number; // Start index of case/default clause range diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 823c29c04df89..0573a5e4fabde 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -7139,8 +7139,8 @@ namespace ts { } /* @internal */ - export function isOptionalChainRoot(node: OptionalChain): node is OptionalChainRoot { - return !!node.questionDotToken; + export function isOptionalChainRoot(node: Node): node is OptionalChainRoot { + return isOptionalChain(node) && !!node.questionDotToken; } /** True if has jsdoc nodes attached to it. */ diff --git a/tests/baselines/reference/controlFlowOptionalChain.errors.txt b/tests/baselines/reference/controlFlowOptionalChain.errors.txt index aef3bc602433a..d0c72ca17cf12 100644 --- a/tests/baselines/reference/controlFlowOptionalChain.errors.txt +++ b/tests/baselines/reference/controlFlowOptionalChain.errors.txt @@ -57,8 +57,8 @@ tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(134,1): error TS declare const f: undefined | ((x: any) => x is number); declare const x: string | number; if (f?.(x)) { - x; // TODO: should be `number` - f; + x; // number + f; // (x: any) => x is number f(x); } else { @@ -76,8 +76,8 @@ tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(134,1): error TS declare const o2: { f(x: any): x is number; } | undefined; if (o2?.f(x)) { - x; // TODO: should be `number` - o2.f; + x; // number + o2.f; // (x: any) => x is number o2?.f; o2?.f(x); } @@ -119,9 +119,9 @@ tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(134,1): error TS declare const o4: { x?: { y: boolean } }; if (o4.x?.y) { - o4.x; - o4.x.y; // TODO: should be `true` - o4.x?.y; // TODO: should be `true` + o4.x; // { y: boolean } + o4.x.y; // true + o4.x?.y; // true } else { o4.x; @@ -141,20 +141,20 @@ tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(134,1): error TS o5.x; o5.x.y; o5.x.y.z; - o5.x.y.z.w; // TODO: should be `true` - o5.x.y.z?.w; // TODO: should be `true` - o5.x?.y.z.w; // TODO: should be `true` - o5.x?.y.z?.w; // TODO: should be `true` + o5.x.y.z.w; // true + o5.x.y.z?.w; // true + o5.x?.y.z.w; // true + o5.x?.y.z?.w; // true } else { o5.x; o5.x?.y; o5.x?.y.z; - o5.x?.y.z?.w; // TODO: should be `false | undefined` + o5.x?.y.z?.w; o5.x.y; ~~~~ !!! error TS2532: Object is possibly 'undefined'. - o5.x.y.z.w; // TODO: should be `false | undefined` + o5.x.y.z.w; ~~~~ !!! error TS2532: Object is possibly 'undefined'. ~~~~~~~~ @@ -183,7 +183,7 @@ tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(134,1): error TS declare const o6: Base | undefined; if (o6?.f()) { - o6; // TODO: should be `Derived` + o6; // Derived o6.f; } else { diff --git a/tests/baselines/reference/controlFlowOptionalChain.js b/tests/baselines/reference/controlFlowOptionalChain.js index 77b0803de0381..58a2873f93aa1 100644 --- a/tests/baselines/reference/controlFlowOptionalChain.js +++ b/tests/baselines/reference/controlFlowOptionalChain.js @@ -26,8 +26,8 @@ d.toString(); declare const f: undefined | ((x: any) => x is number); declare const x: string | number; if (f?.(x)) { - x; // TODO: should be `number` - f; + x; // number + f; // (x: any) => x is number f(x); } else { @@ -41,8 +41,8 @@ f(x); declare const o2: { f(x: any): x is number; } | undefined; if (o2?.f(x)) { - x; // TODO: should be `number` - o2.f; + x; // number + o2.f; // (x: any) => x is number o2?.f; o2?.f(x); } @@ -74,9 +74,9 @@ o3.x; declare const o4: { x?: { y: boolean } }; if (o4.x?.y) { - o4.x; - o4.x.y; // TODO: should be `true` - o4.x?.y; // TODO: should be `true` + o4.x; // { y: boolean } + o4.x.y; // true + o4.x?.y; // true } else { o4.x; @@ -92,18 +92,18 @@ if (o5.x?.y.z?.w) { o5.x; o5.x.y; o5.x.y.z; - o5.x.y.z.w; // TODO: should be `true` - o5.x.y.z?.w; // TODO: should be `true` - o5.x?.y.z.w; // TODO: should be `true` - o5.x?.y.z?.w; // TODO: should be `true` + o5.x.y.z.w; // true + o5.x.y.z?.w; // true + o5.x?.y.z.w; // true + o5.x?.y.z?.w; // true } else { o5.x; o5.x?.y; o5.x?.y.z; - o5.x?.y.z?.w; // TODO: should be `false | undefined` + o5.x?.y.z?.w; o5.x.y; - o5.x.y.z.w; // TODO: should be `false | undefined` + o5.x.y.z.w; } o5.x; o5.x?.y; @@ -122,7 +122,7 @@ interface Derived extends Base { declare const o6: Base | undefined; if (o6?.f()) { - o6; // TODO: should be `Derived` + o6; // Derived o6.f; } else { @@ -177,8 +177,8 @@ var d; (_d = o) === null || _d === void 0 ? void 0 : _d.x(d = 1); d.toString(); if ((_e = f) === null || _e === void 0 ? void 0 : _e(x)) { - x; // TODO: should be `number` - f; + x; // number + f; // (x: any) => x is number f(x); } else { @@ -190,8 +190,8 @@ x; f; f(x); if ((_f = o2) === null || _f === void 0 ? void 0 : _f.f(x)) { - x; // TODO: should be `number` - o2.f; + x; // number + o2.f; // (x: any) => x is number (_g = o2) === null || _g === void 0 ? void 0 : _g.f; (_h = o2) === null || _h === void 0 ? void 0 : _h.f(x); } @@ -219,9 +219,9 @@ o3; (_p = o3) === null || _p === void 0 ? void 0 : _p.x; o3.x; if ((_q = o4.x) === null || _q === void 0 ? void 0 : _q.y) { - o4.x; - o4.x.y; // TODO: should be `true` - (_r = o4.x) === null || _r === void 0 ? void 0 : _r.y; // TODO: should be `true` + o4.x; // { y: boolean } + o4.x.y; // true + (_r = o4.x) === null || _r === void 0 ? void 0 : _r.y; // true } else { o4.x; @@ -235,18 +235,18 @@ if ((_v = (_u = o5.x) === null || _u === void 0 ? void 0 : _u.y.z) === null || _ o5.x; o5.x.y; o5.x.y.z; - o5.x.y.z.w; // TODO: should be `true` - (_w = o5.x.y.z) === null || _w === void 0 ? void 0 : _w.w; // TODO: should be `true` - (_x = o5.x) === null || _x === void 0 ? void 0 : _x.y.z.w; // TODO: should be `true` - (_z = (_y = o5.x) === null || _y === void 0 ? void 0 : _y.y.z) === null || _z === void 0 ? void 0 : _z.w; // TODO: should be `true` + o5.x.y.z.w; // true + (_w = o5.x.y.z) === null || _w === void 0 ? void 0 : _w.w; // true + (_x = o5.x) === null || _x === void 0 ? void 0 : _x.y.z.w; // true + (_z = (_y = o5.x) === null || _y === void 0 ? void 0 : _y.y.z) === null || _z === void 0 ? void 0 : _z.w; // true } else { o5.x; (_0 = o5.x) === null || _0 === void 0 ? void 0 : _0.y; (_1 = o5.x) === null || _1 === void 0 ? void 0 : _1.y.z; - (_3 = (_2 = o5.x) === null || _2 === void 0 ? void 0 : _2.y.z) === null || _3 === void 0 ? void 0 : _3.w; // TODO: should be `false | undefined` + (_3 = (_2 = o5.x) === null || _2 === void 0 ? void 0 : _2.y.z) === null || _3 === void 0 ? void 0 : _3.w; o5.x.y; - o5.x.y.z.w; // TODO: should be `false | undefined` + o5.x.y.z.w; } o5.x; (_4 = o5.x) === null || _4 === void 0 ? void 0 : _4.y; @@ -255,7 +255,7 @@ o5.x; o5.x.y; o5.x.y.z.w; if ((_8 = o6) === null || _8 === void 0 ? void 0 : _8.f()) { - o6; // TODO: should be `Derived` + o6; // Derived o6.f; } else { diff --git a/tests/baselines/reference/controlFlowOptionalChain.symbols b/tests/baselines/reference/controlFlowOptionalChain.symbols index 29651e88f4093..21f1de2a41026 100644 --- a/tests/baselines/reference/controlFlowOptionalChain.symbols +++ b/tests/baselines/reference/controlFlowOptionalChain.symbols @@ -75,10 +75,10 @@ if (f?.(x)) { >f : Symbol(f, Decl(controlFlowOptionalChain.ts, 24, 13)) >x : Symbol(x, Decl(controlFlowOptionalChain.ts, 25, 13)) - x; // TODO: should be `number` + x; // number >x : Symbol(x, Decl(controlFlowOptionalChain.ts, 25, 13)) - f; + f; // (x: any) => x is number >f : Symbol(f, Decl(controlFlowOptionalChain.ts, 24, 13)) f(x); @@ -118,10 +118,10 @@ if (o2?.f(x)) { >f : Symbol(f, Decl(controlFlowOptionalChain.ts, 40, 19)) >x : Symbol(x, Decl(controlFlowOptionalChain.ts, 25, 13)) - x; // TODO: should be `number` + x; // number >x : Symbol(x, Decl(controlFlowOptionalChain.ts, 25, 13)) - o2.f; + o2.f; // (x: any) => x is number >o2.f : Symbol(f, Decl(controlFlowOptionalChain.ts, 40, 19)) >o2 : Symbol(o2, Decl(controlFlowOptionalChain.ts, 40, 13)) >f : Symbol(f, Decl(controlFlowOptionalChain.ts, 40, 19)) @@ -234,19 +234,19 @@ if (o4.x?.y) { >x : Symbol(x, Decl(controlFlowOptionalChain.ts, 73, 19)) >y : Symbol(y, Decl(controlFlowOptionalChain.ts, 73, 25)) - o4.x; + o4.x; // { y: boolean } >o4.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 73, 19)) >o4 : Symbol(o4, Decl(controlFlowOptionalChain.ts, 73, 13)) >x : Symbol(x, Decl(controlFlowOptionalChain.ts, 73, 19)) - o4.x.y; // TODO: should be `true` + o4.x.y; // true >o4.x.y : Symbol(y, Decl(controlFlowOptionalChain.ts, 73, 25)) >o4.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 73, 19)) >o4 : Symbol(o4, Decl(controlFlowOptionalChain.ts, 73, 13)) >x : Symbol(x, Decl(controlFlowOptionalChain.ts, 73, 19)) >y : Symbol(y, Decl(controlFlowOptionalChain.ts, 73, 25)) - o4.x?.y; // TODO: should be `true` + o4.x?.y; // true >o4.x?.y : Symbol(y, Decl(controlFlowOptionalChain.ts, 73, 25)) >o4.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 73, 19)) >o4 : Symbol(o4, Decl(controlFlowOptionalChain.ts, 73, 13)) @@ -331,7 +331,7 @@ if (o5.x?.y.z?.w) { >y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) >z : Symbol(z, Decl(controlFlowOptionalChain.ts, 88, 30)) - o5.x.y.z.w; // TODO: should be `true` + o5.x.y.z.w; // true >o5.x.y.z.w : Symbol(w, Decl(controlFlowOptionalChain.ts, 88, 36)) >o5.x.y.z : Symbol(z, Decl(controlFlowOptionalChain.ts, 88, 30)) >o5.x.y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) @@ -342,7 +342,7 @@ if (o5.x?.y.z?.w) { >z : Symbol(z, Decl(controlFlowOptionalChain.ts, 88, 30)) >w : Symbol(w, Decl(controlFlowOptionalChain.ts, 88, 36)) - o5.x.y.z?.w; // TODO: should be `true` + o5.x.y.z?.w; // true >o5.x.y.z?.w : Symbol(w, Decl(controlFlowOptionalChain.ts, 88, 36)) >o5.x.y.z : Symbol(z, Decl(controlFlowOptionalChain.ts, 88, 30)) >o5.x.y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) @@ -353,7 +353,7 @@ if (o5.x?.y.z?.w) { >z : Symbol(z, Decl(controlFlowOptionalChain.ts, 88, 30)) >w : Symbol(w, Decl(controlFlowOptionalChain.ts, 88, 36)) - o5.x?.y.z.w; // TODO: should be `true` + o5.x?.y.z.w; // true >o5.x?.y.z.w : Symbol(w, Decl(controlFlowOptionalChain.ts, 88, 36)) >o5.x?.y.z : Symbol(z, Decl(controlFlowOptionalChain.ts, 88, 30)) >o5.x?.y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) @@ -364,7 +364,7 @@ if (o5.x?.y.z?.w) { >z : Symbol(z, Decl(controlFlowOptionalChain.ts, 88, 30)) >w : Symbol(w, Decl(controlFlowOptionalChain.ts, 88, 36)) - o5.x?.y.z?.w; // TODO: should be `true` + o5.x?.y.z?.w; // true >o5.x?.y.z?.w : Symbol(w, Decl(controlFlowOptionalChain.ts, 88, 36)) >o5.x?.y.z : Symbol(z, Decl(controlFlowOptionalChain.ts, 88, 30)) >o5.x?.y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) @@ -397,7 +397,7 @@ else { >y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) >z : Symbol(z, Decl(controlFlowOptionalChain.ts, 88, 30)) - o5.x?.y.z?.w; // TODO: should be `false | undefined` + o5.x?.y.z?.w; >o5.x?.y.z?.w : Symbol(w, Decl(controlFlowOptionalChain.ts, 88, 36)) >o5.x?.y.z : Symbol(z, Decl(controlFlowOptionalChain.ts, 88, 30)) >o5.x?.y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) @@ -415,7 +415,7 @@ else { >x : Symbol(x, Decl(controlFlowOptionalChain.ts, 88, 19)) >y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) - o5.x.y.z.w; // TODO: should be `false | undefined` + o5.x.y.z.w; >o5.x.y.z.w : Symbol(w, Decl(controlFlowOptionalChain.ts, 88, 36)) >o5.x.y.z : Symbol(z, Decl(controlFlowOptionalChain.ts, 88, 30)) >o5.x.y : Symbol(y, Decl(controlFlowOptionalChain.ts, 88, 25)) @@ -501,7 +501,7 @@ if (o6?.f()) { >o6 : Symbol(o6, Decl(controlFlowOptionalChain.ts, 121, 13)) >f : Symbol(Base.f, Decl(controlFlowOptionalChain.ts, 113, 16)) - o6; // TODO: should be `Derived` + o6; // Derived >o6 : Symbol(o6, Decl(controlFlowOptionalChain.ts, 121, 13)) o6.f; diff --git a/tests/baselines/reference/controlFlowOptionalChain.types b/tests/baselines/reference/controlFlowOptionalChain.types index bddbb3d207017..a93c5b0b43f04 100644 --- a/tests/baselines/reference/controlFlowOptionalChain.types +++ b/tests/baselines/reference/controlFlowOptionalChain.types @@ -95,16 +95,16 @@ if (f?.(x)) { >f : ((x: any) => x is number) | undefined >x : string | number - x; // TODO: should be `number` ->x : string | number + x; // number +>x : number - f; + f; // (x: any) => x is number >f : (x: any) => x is number f(x); >f(x) : boolean >f : (x: any) => x is number ->x : string | number +>x : number } else { x; @@ -141,10 +141,10 @@ if (o2?.f(x)) { >f : ((x: any) => x is number) | undefined >x : string | number - x; // TODO: should be `number` ->x : string | number + x; // number +>x : number - o2.f; + o2.f; // (x: any) => x is number >o2.f : (x: any) => x is number >o2 : { f(x: any): x is number; } >f : (x: any) => x is number @@ -159,7 +159,7 @@ if (o2?.f(x)) { >o2?.f : (x: any) => x is number >o2 : { f(x: any): x is number; } >f : (x: any) => x is number ->x : string | number +>x : number } else { x; @@ -260,24 +260,24 @@ if (o4.x?.y) { >x : { y: boolean; } | undefined >y : boolean | undefined - o4.x; + o4.x; // { y: boolean } >o4.x : { y: boolean; } >o4 : { x?: { y: boolean; } | undefined; } >x : { y: boolean; } - o4.x.y; // TODO: should be `true` ->o4.x.y : boolean + o4.x.y; // true +>o4.x.y : true >o4.x : { y: boolean; } >o4 : { x?: { y: boolean; } | undefined; } >x : { y: boolean; } ->y : boolean +>y : true - o4.x?.y; // TODO: should be `true` ->o4.x?.y : boolean + o4.x?.y; // true +>o4.x?.y : true >o4.x : { y: boolean; } >o4 : { x?: { y: boolean; } | undefined; } >x : { y: boolean; } ->y : boolean +>y : true } else { o4.x; @@ -357,8 +357,8 @@ if (o5.x?.y.z?.w) { >y : { z?: { w: boolean; } | undefined; } >z : { w: boolean; } - o5.x.y.z.w; // TODO: should be `true` ->o5.x.y.z.w : boolean + o5.x.y.z.w; // true +>o5.x.y.z.w : true >o5.x.y.z : { w: boolean; } >o5.x.y : { z?: { w: boolean; } | undefined; } >o5.x : { y: { z?: { w: boolean; } | undefined; }; } @@ -366,10 +366,10 @@ if (o5.x?.y.z?.w) { >x : { y: { z?: { w: boolean; } | undefined; }; } >y : { z?: { w: boolean; } | undefined; } >z : { w: boolean; } ->w : boolean +>w : true - o5.x.y.z?.w; // TODO: should be `true` ->o5.x.y.z?.w : boolean + o5.x.y.z?.w; // true +>o5.x.y.z?.w : true >o5.x.y.z : { w: boolean; } >o5.x.y : { z?: { w: boolean; } | undefined; } >o5.x : { y: { z?: { w: boolean; } | undefined; }; } @@ -377,10 +377,10 @@ if (o5.x?.y.z?.w) { >x : { y: { z?: { w: boolean; } | undefined; }; } >y : { z?: { w: boolean; } | undefined; } >z : { w: boolean; } ->w : boolean +>w : true - o5.x?.y.z.w; // TODO: should be `true` ->o5.x?.y.z.w : boolean + o5.x?.y.z.w; // true +>o5.x?.y.z.w : true >o5.x?.y.z : { w: boolean; } >o5.x?.y : { z?: { w: boolean; } | undefined; } >o5.x : { y: { z?: { w: boolean; } | undefined; }; } @@ -388,10 +388,10 @@ if (o5.x?.y.z?.w) { >x : { y: { z?: { w: boolean; } | undefined; }; } >y : { z?: { w: boolean; } | undefined; } >z : { w: boolean; } ->w : boolean +>w : true - o5.x?.y.z?.w; // TODO: should be `true` ->o5.x?.y.z?.w : boolean + o5.x?.y.z?.w; // true +>o5.x?.y.z?.w : true >o5.x?.y.z : { w: boolean; } >o5.x?.y : { z?: { w: boolean; } | undefined; } >o5.x : { y: { z?: { w: boolean; } | undefined; }; } @@ -399,7 +399,7 @@ if (o5.x?.y.z?.w) { >x : { y: { z?: { w: boolean; } | undefined; }; } >y : { z?: { w: boolean; } | undefined; } >z : { w: boolean; } ->w : boolean +>w : true } else { o5.x; @@ -423,7 +423,7 @@ else { >y : { z?: { w: boolean; } | undefined; } | undefined >z : { w: boolean; } | undefined - o5.x?.y.z?.w; // TODO: should be `false | undefined` + o5.x?.y.z?.w; >o5.x?.y.z?.w : boolean | undefined >o5.x?.y.z : { w: boolean; } | undefined >o5.x?.y : { z?: { w: boolean; } | undefined; } | undefined @@ -441,7 +441,7 @@ else { >x : { y: { z?: { w: boolean; } | undefined; }; } | undefined >y : { z?: { w: boolean; } | undefined; } - o5.x.y.z.w; // TODO: should be `false | undefined` + o5.x.y.z.w; >o5.x.y.z.w : boolean >o5.x.y.z : { w: boolean; } | undefined >o5.x.y : { z?: { w: boolean; } | undefined; } @@ -521,12 +521,12 @@ if (o6?.f()) { >o6 : Base | undefined >f : (() => this is Derived) | undefined - o6; // TODO: should be `Derived` ->o6 : Base + o6; // Derived +>o6 : Derived o6.f; >o6.f : () => this is Derived ->o6 : Base +>o6 : Derived >f : () => this is Derived } else { diff --git a/tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts b/tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts index 2e07ad062c1bf..dadb1b36076db 100644 --- a/tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts +++ b/tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts @@ -28,8 +28,8 @@ d.toString(); declare const f: undefined | ((x: any) => x is number); declare const x: string | number; if (f?.(x)) { - x; // TODO: should be `number` - f; + x; // number + f; // (x: any) => x is number f(x); } else { @@ -43,8 +43,8 @@ f(x); declare const o2: { f(x: any): x is number; } | undefined; if (o2?.f(x)) { - x; // TODO: should be `number` - o2.f; + x; // number + o2.f; // (x: any) => x is number o2?.f; o2?.f(x); } @@ -76,9 +76,9 @@ o3.x; declare const o4: { x?: { y: boolean } }; if (o4.x?.y) { - o4.x; - o4.x.y; // TODO: should be `true` - o4.x?.y; // TODO: should be `true` + o4.x; // { y: boolean } + o4.x.y; // true + o4.x?.y; // true } else { o4.x; @@ -94,18 +94,18 @@ if (o5.x?.y.z?.w) { o5.x; o5.x.y; o5.x.y.z; - o5.x.y.z.w; // TODO: should be `true` - o5.x.y.z?.w; // TODO: should be `true` - o5.x?.y.z.w; // TODO: should be `true` - o5.x?.y.z?.w; // TODO: should be `true` + o5.x.y.z.w; // true + o5.x.y.z?.w; // true + o5.x?.y.z.w; // true + o5.x?.y.z?.w; // true } else { o5.x; o5.x?.y; o5.x?.y.z; - o5.x?.y.z?.w; // TODO: should be `false | undefined` + o5.x?.y.z?.w; o5.x.y; - o5.x.y.z.w; // TODO: should be `false | undefined` + o5.x.y.z.w; } o5.x; o5.x?.y; @@ -124,7 +124,7 @@ interface Derived extends Base { declare const o6: Base | undefined; if (o6?.f()) { - o6; // TODO: should be `Derived` + o6; // Derived o6.f; } else { From aaa30f49c4a381a944d2e7b496018dacb9399f76 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Sat, 28 Sep 2019 17:46:31 -0700 Subject: [PATCH 15/19] Fix ?. insertion for completions on type variables --- src/compiler/checker.ts | 1 + src/compiler/types.ts | 1 + src/services/completions.ts | 22 ++++++++++++------- src/services/services.ts | 3 +++ src/services/types.ts | 1 + ...ompletionNoAutoInsertQuestionDotForThis.ts | 20 +++++++++++++++++ ...NoAutoInsertQuestionDotForTypeParameter.ts | 19 ++++++++++++++++ 7 files changed, 59 insertions(+), 8 deletions(-) create mode 100644 tests/cases/fourslash/completionNoAutoInsertQuestionDotForThis.ts create mode 100644 tests/cases/fourslash/completionNoAutoInsertQuestionDotForTypeParameter.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 93163277167c8..2f1c4f13201ec 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -384,6 +384,7 @@ namespace ts { getParameterType: getTypeAtPosition, getPromisedTypeOfPromise, getReturnTypeOfSignature, + isNullableType, getNullableType, getNonNullableType, getNonOptionalType: removeOptionalTypeMarker, diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 537227c1f1ef8..35fe948a72df0 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3291,6 +3291,7 @@ namespace ts { getNullableType(type: Type, flags: TypeFlags): Type; getNonNullableType(type: Type): Type; /* @internal */ getNonOptionalType(type: Type): Type; + /* @internal */ isNullableType(type: Type): boolean; getTypeArguments(type: TypeReference): readonly Type[]; // TODO: GH#18217 `xToDeclaration` calls are frequently asserted as defined. diff --git a/src/services/completions.ts b/src/services/completions.ts index ddf3acfcadb5b..f01ac060d1dc5 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -1033,10 +1033,13 @@ namespace ts.Completions { if (!isTypeLocation && symbol.declarations && symbol.declarations.some(d => d.kind !== SyntaxKind.SourceFile && d.kind !== SyntaxKind.ModuleDeclaration && d.kind !== SyntaxKind.EnumDeclaration)) { - const type = typeChecker.getTypeOfSymbolAtLocation(symbol, node).getNonOptionalType(); - const nonNullType = type.getNonNullableType(); - const insertQuestionDot = isRightOfDot && !isRightOfQuestionDot && type !== nonNullType; - addTypeProperties(nonNullType, !!(node.flags & NodeFlags.AwaitContext), insertQuestionDot); + let type = typeChecker.getTypeOfSymbolAtLocation(symbol, node).getNonOptionalType(); + let insertQuestionDot = false; + if (type.isNullableType()) { + insertQuestionDot = isRightOfDot && !isRightOfQuestionDot; + type = type.getNonNullableType(); + } + addTypeProperties(type, !!(node.flags & NodeFlags.AwaitContext), insertQuestionDot); } return; @@ -1051,10 +1054,13 @@ namespace ts.Completions { } if (!isTypeLocation) { - const type = typeChecker.getTypeAtLocation(node).getNonOptionalType(); - const nonNullType = type.getNonNullableType(); - const insertQuestionDot = isRightOfDot && !isRightOfQuestionDot && type !== nonNullType; - addTypeProperties(nonNullType, !!(node.flags & NodeFlags.AwaitContext), insertQuestionDot); + let type = typeChecker.getTypeAtLocation(node).getNonOptionalType(); + let insertQuestionDot = false; + if (type.isNullableType()) { + insertQuestionDot = isRightOfDot && !isRightOfQuestionDot; + type = type.getNonNullableType(); + } + addTypeProperties(type, !!(node.flags & NodeFlags.AwaitContext), insertQuestionDot); } } diff --git a/src/services/services.ts b/src/services/services.ts index 663504b2027fe..90996d336d051 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -409,6 +409,9 @@ namespace ts { getBaseTypes(): BaseType[] | undefined { return this.isClassOrInterface() ? this.checker.getBaseTypes(this) : undefined; } + isNullableType(): boolean { + return this.checker.isNullableType(this); + } getNonNullableType(): Type { return this.checker.getNonNullableType(this); } diff --git a/src/services/types.ts b/src/services/types.ts index bae725542e8b5..31655b20dc9e5 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -53,6 +53,7 @@ namespace ts { getBaseTypes(): BaseType[] | undefined; getNonNullableType(): Type; /*@internal*/ getNonOptionalType(): Type; + /*@internal*/ isNullableType(): boolean; getConstraint(): Type | undefined; getDefault(): Type | undefined; diff --git a/tests/cases/fourslash/completionNoAutoInsertQuestionDotForThis.ts b/tests/cases/fourslash/completionNoAutoInsertQuestionDotForThis.ts new file mode 100644 index 0000000000000..4a99155bc7944 --- /dev/null +++ b/tests/cases/fourslash/completionNoAutoInsertQuestionDotForThis.ts @@ -0,0 +1,20 @@ +/// +// @strict: true + +//// class Address { +//// city: string = ""; +//// "postal code": string = ""; +//// method() { +//// this[|./**/|] +//// } +//// } + +verify.completions({ + marker: "", + exact: [ + { name: "city", text: "(property) Address.city: string", insertText: undefined }, + { name: "postal code", text: "(property) Address[\"postal code\"]: string", insertText: "[\"postal code\"]", replacementSpan: test.ranges()[0] }, + { name: "method" } + ], + preferences: { includeInsertTextCompletions: true }, +}); diff --git a/tests/cases/fourslash/completionNoAutoInsertQuestionDotForTypeParameter.ts b/tests/cases/fourslash/completionNoAutoInsertQuestionDotForTypeParameter.ts new file mode 100644 index 0000000000000..6061ef37df21a --- /dev/null +++ b/tests/cases/fourslash/completionNoAutoInsertQuestionDotForTypeParameter.ts @@ -0,0 +1,19 @@ +/// +// @strict: true + +//// interface Address { +//// city: string = ""; +//// "postal code": string = ""; +//// } +//// function f(x: T) { +//// x[|./**/|] +//// } + +verify.completions({ + marker: "", + exact: [ + { name: "city", text: "(property) Address.city: string" }, + { name: "postal code", text: "(property) Address[\"postal code\"]: string", insertText: "[\"postal code\"]", replacementSpan: test.ranges()[0] } + ], + preferences: { includeInsertTextCompletions: true }, +}); From 5ea7cb5b2baa47572e0c2f69dec22a017ed1c5c9 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Sat, 28 Sep 2019 17:52:00 -0700 Subject: [PATCH 16/19] Accept API baseline change --- tests/baselines/reference/api/tsserverlibrary.d.ts | 11 ++--------- tests/baselines/reference/api/typescript.d.ts | 11 ++--------- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 0ab426d0fd86a..8c8b5207a0ba2 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -1697,13 +1697,10 @@ declare namespace ts { Shared = 2048, PreFinally = 4096, AfterFinally = 8192, - Present = 16384, - Missing = 32768, Label = 12, - Condition = 96, - OptionalChain = 49152 + Condition = 96 } - export type FlowNode = AfterFinallyFlow | PreFinallyFlow | FlowStart | FlowLabel | FlowAssignment | FlowCall | FlowCondition | FlowOptionalChain | FlowSwitchClause | FlowArrayMutation; + export type FlowNode = AfterFinallyFlow | PreFinallyFlow | FlowStart | FlowLabel | FlowAssignment | FlowCall | FlowCondition | FlowSwitchClause | FlowArrayMutation; export interface FlowNodeBase { flags: FlowFlags; id?: number; @@ -1736,10 +1733,6 @@ declare namespace ts { node: Expression; antecedent: FlowNode; } - export interface FlowOptionalChain extends FlowNodeBase { - node: Expression; - antecedent: FlowNode; - } export interface FlowSwitchClause extends FlowNodeBase { switchStatement: SwitchStatement; clauseStart: number; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index de93daad7b312..f646906340700 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -1697,13 +1697,10 @@ declare namespace ts { Shared = 2048, PreFinally = 4096, AfterFinally = 8192, - Present = 16384, - Missing = 32768, Label = 12, - Condition = 96, - OptionalChain = 49152 + Condition = 96 } - export type FlowNode = AfterFinallyFlow | PreFinallyFlow | FlowStart | FlowLabel | FlowAssignment | FlowCall | FlowCondition | FlowOptionalChain | FlowSwitchClause | FlowArrayMutation; + export type FlowNode = AfterFinallyFlow | PreFinallyFlow | FlowStart | FlowLabel | FlowAssignment | FlowCall | FlowCondition | FlowSwitchClause | FlowArrayMutation; export interface FlowNodeBase { flags: FlowFlags; id?: number; @@ -1736,10 +1733,6 @@ declare namespace ts { node: Expression; antecedent: FlowNode; } - export interface FlowOptionalChain extends FlowNodeBase { - node: Expression; - antecedent: FlowNode; - } export interface FlowSwitchClause extends FlowNodeBase { switchStatement: SwitchStatement; clauseStart: number; From 74638602965b4fd07b940a90aa19d03ae145a35c Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Sat, 28 Sep 2019 22:06:41 -0700 Subject: [PATCH 17/19] Clean up types --- src/compiler/binder.ts | 8 ++++---- src/compiler/types.ts | 40 --------------------------------------- src/compiler/utilities.ts | 13 ------------- 3 files changed, 4 insertions(+), 57 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index b58c099acfa02..2527f0b431a19 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1581,7 +1581,7 @@ namespace ts { } } - function bindOptionalChain(node: ValidOptionalChain, trueTarget: FlowLabel, falseTarget: FlowLabel) { + function bindOptionalChain(node: OptionalChain, trueTarget: FlowLabel, falseTarget: FlowLabel) { // For an optional chain, we emulate the behavior of a logical expression: // // a?.b -> a && a.b @@ -1605,7 +1605,7 @@ namespace ts { } } - function bindOptionalChainFlow(node: ValidOptionalChain) { + function bindOptionalChainFlow(node: OptionalChain) { if (isTopLevelLogicalExpression(node)) { const postExpressionLabel = createBranchLabel(); bindOptionalChain(node, postExpressionLabel, postExpressionLabel); @@ -1617,7 +1617,7 @@ namespace ts { } function bindAccessExpressionFlow(node: AccessExpression) { - if (isValidOptionalChain(node)) { + if (isOptionalChain(node)) { bindOptionalChainFlow(node); } else { @@ -1626,7 +1626,7 @@ namespace ts { } function bindCallExpressionFlow(node: CallExpression) { - if (isValidOptionalChain(node)) { + if (isOptionalChain(node)) { bindOptionalChainFlow(node); } else { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 35fe948a72df0..1c97f2121a0cd 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1806,17 +1806,6 @@ namespace ts { questionDotToken: QuestionDotToken; } - /* @internal */ - export interface ValidPropertyAccessChainLink extends PropertyAccessChain { - questionDotToken: undefined; - expression: ValidOptionalChain; - } - - /* @internal */ - export type ValidPropertyAccessChain = - | PropertyAccessChainRoot - | ValidPropertyAccessChainLink; - export interface SuperPropertyAccessExpression extends PropertyAccessExpression { expression: SuperExpression; } @@ -1843,17 +1832,6 @@ namespace ts { questionDotToken: QuestionDotToken; } - /* @internal */ - export interface ValidElementAccessChainLink extends ElementAccessChain { - questionDotToken: undefined; - expression: ValidOptionalChain; - } - - /* @internal */ - export type ValidElementAccessChain = - | ElementAccessChainRoot - | ValidElementAccessChainLink; - export interface SuperElementAccessExpression extends ElementAccessExpression { expression: SuperExpression; } @@ -1878,17 +1856,6 @@ namespace ts { questionDotToken: QuestionDotToken; } - /* @internal */ - export interface ValidCallChainLink extends CallChain { - questionDotToken: undefined; - expression: ValidOptionalChain; - } - - /* @internal */ - export type ValidCallChain = - | CallChainRoot - | ValidCallChainLink; - export type OptionalChain = | PropertyAccessChain | ElementAccessChain @@ -1902,13 +1869,6 @@ namespace ts { | CallChainRoot ; - /* @internal */ - export type ValidOptionalChain = - | ValidPropertyAccessChain - | ValidElementAccessChain - | ValidCallChain - ; - /** @internal */ export type BindableObjectDefinePropertyCall = CallExpression & { arguments: { 0: EntityNameExpression, 1: StringLiteralLike | NumericLiteral, 2: ObjectLiteralExpression } }; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 0573a5e4fabde..88aaf144c51d3 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -7125,19 +7125,6 @@ namespace ts { return node.kind === SyntaxKind.GetAccessor; } - /** - * Tests whether an OptionalChain is valid. We can parse an invalid optional chain if it contains an - * invalid TaggedTemplateChain per the ECMAScript syntax. - */ - /* @internal */ - export function isValidOptionalChain(node: Expression): node is ValidOptionalChain { - while (isOptionalChain(node)) { - if (node.questionDotToken) return true; - node = node.expression; - } - return false; - } - /* @internal */ export function isOptionalChainRoot(node: Node): node is OptionalChainRoot { return isOptionalChain(node) && !!node.questionDotToken; From 0828674a86f968aa73bcb19af1f847534d7432be Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Mon, 30 Sep 2019 01:40:27 -0700 Subject: [PATCH 18/19] improve control-flow debug output --- src/compiler/binder.ts | 69 ++++-- src/compiler/checker.ts | 2 +- src/compiler/core.ts | 2 +- src/compiler/debug.ts | 490 +++++++++++++++++++++++--------------- src/compiler/utilities.ts | 7 + src/services/services.ts | 6 + 6 files changed, 374 insertions(+), 202 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 2527f0b431a19..c0ae9f0448f6e 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -209,10 +209,11 @@ namespace ts { let symbolCount = 0; let Symbol: new (flags: SymbolFlags, name: __String) => Symbol; + let FlowNode: new (flags: FlowFlags) => FlowNodeBase; let classifiableNames: UnderscoreEscapedMap; - const unreachableFlow: FlowNode = { flags: FlowFlags.Unreachable }; - const reportedUnreachableFlow: FlowNode = { flags: FlowFlags.Unreachable }; + let unreachableFlow: FlowNode; + let reportedUnreachableFlow: FlowNode; // state used to aggregate transform flags during bind. let subtreeTransformFlags: TransformFlags = TransformFlags.None; @@ -227,6 +228,17 @@ namespace ts { return createDiagnosticForNodeInSourceFile(getSourceFileOfNode(node) || file, node, message, arg0, arg1, arg2); } + function initializeBinder() { + const symbolConstructor = objectAllocator.getSymbolConstructor(); + const flowNodeConstructor = objectAllocator.getFlowNodeConstructor(); + if (Symbol !== symbolConstructor || FlowNode !== flowNodeConstructor) { + Symbol = symbolConstructor; + FlowNode = flowNodeConstructor; + unreachableFlow = new FlowNode(FlowFlags.Unreachable); + reportedUnreachableFlow = new FlowNode(FlowFlags.Unreachable); + } + } + function bindSourceFile(f: SourceFile, opts: CompilerOptions) { file = f; options = opts; @@ -236,7 +248,7 @@ namespace ts { symbolCount = 0; skipTransformFlagAggregation = file.isDeclarationFile; - Symbol = objectAllocator.getSymbolConstructor(); + initializeBinder(); if (!file.locals) { bind(file); @@ -623,7 +635,7 @@ namespace ts { // A non-async, non-generator IIFE is considered part of the containing control flow. Return statements behave // similarly to break statements that exit to a label just past the statement body. if (!isIIFE) { - currentFlow = { flags: FlowFlags.Start }; + currentFlow = createFlowStart(); if (containerFlags & (ContainerFlags.IsFunctionExpression | ContainerFlags.IsObjectLiteralOrClassExpressionMethod)) { currentFlow.node = node; } @@ -917,11 +929,15 @@ namespace ts { } function createBranchLabel(): FlowLabel { - return { flags: FlowFlags.BranchLabel, antecedents: undefined }; + const flow = new FlowNode(FlowFlags.BranchLabel) as FlowLabel; + flow.antecedents = undefined; + return flow; } function createLoopLabel(): FlowLabel { - return { flags: FlowFlags.LoopLabel, antecedents: undefined }; + const flow = new FlowNode(FlowFlags.LoopLabel) as FlowLabel; + flow.antecedents = undefined; + return flow; } function setFlowNodeReferenced(flow: FlowNode) { @@ -936,6 +952,10 @@ namespace ts { } } + function createFlowStart(): FlowStart { + return new FlowNode(FlowFlags.Start) as FlowStart; + } + function createFlowCondition(flags: FlowFlags, antecedent: FlowNode, expression: Expression | undefined): FlowNode { if (antecedent.flags & FlowFlags.Unreachable) { return antecedent; @@ -953,7 +973,10 @@ namespace ts { return antecedent; } setFlowNodeReferenced(antecedent); - return flowNodeCreated({ flags, antecedent, node: expression }); + const flow = new FlowNode(flags) as FlowCondition; + flow.antecedent = antecedent; + flow.node = expression; + return flowNodeCreated(flow); } function createFlowSwitchClause(antecedent: FlowNode, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number): FlowNode { @@ -961,22 +984,36 @@ namespace ts { return antecedent; } setFlowNodeReferenced(antecedent); - return flowNodeCreated({ flags: FlowFlags.SwitchClause, antecedent, switchStatement, clauseStart, clauseEnd }); + const flow = new FlowNode(FlowFlags.SwitchClause) as FlowSwitchClause; + flow.antecedent = antecedent; + flow.switchStatement = switchStatement; + flow.clauseStart = clauseStart; + flow.clauseEnd = clauseEnd; + return flowNodeCreated(flow); } function createFlowAssignment(antecedent: FlowNode, node: Expression | VariableDeclaration | BindingElement): FlowNode { setFlowNodeReferenced(antecedent); - return flowNodeCreated({ flags: FlowFlags.Assignment, antecedent, node }); + const flow = new FlowNode(FlowFlags.Assignment) as FlowAssignment; + flow.antecedent = antecedent; + flow.node = node; + return flowNodeCreated(flow); } function createFlowCall(antecedent: FlowNode, node: CallExpression): FlowNode { setFlowNodeReferenced(antecedent); - return flowNodeCreated({ flags: FlowFlags.Call, antecedent, node }); + const flow = new FlowNode(FlowFlags.Call) as FlowCall; + flow.antecedent = antecedent; + flow.node = node; + return flowNodeCreated(flow); } function createFlowArrayMutation(antecedent: FlowNode, node: CallExpression | BinaryExpression): FlowNode { setFlowNodeReferenced(antecedent); - return flowNodeCreated({ flags: FlowFlags.ArrayMutation, antecedent, node }); + const flow = new FlowNode(FlowFlags.ArrayMutation) as FlowArrayMutation; + flow.antecedent = antecedent; + flow.node = node; + return flowNodeCreated(flow); } function finishFlowLabel(flow: FlowLabel): FlowNode { @@ -1259,7 +1296,9 @@ namespace ts { // // extra edges that we inject allows to control this behavior // if when walking the flow we step on post-finally edge - we can mark matching pre-finally edge as locked so it will be skipped. - const preFinallyFlow: PreFinallyFlow = { flags: FlowFlags.PreFinally, antecedent: preFinallyPrior, lock: {} }; + const preFinallyFlow = new FlowNode(FlowFlags.PreFinally) as PreFinallyFlow; + preFinallyFlow.antecedent = preFinallyPrior; + preFinallyFlow.lock = {}; addAntecedent(preFinallyLabel, preFinallyFlow); currentFlow = finishFlowLabel(preFinallyLabel); @@ -1278,7 +1317,9 @@ namespace ts { } } if (!(currentFlow.flags & FlowFlags.Unreachable)) { - const afterFinallyFlow: AfterFinallyFlow = flowNodeCreated({ flags: FlowFlags.AfterFinally, antecedent: currentFlow }); + const afterFinallyFlow = new FlowNode(FlowFlags.AfterFinally) as AfterFinallyFlow; + afterFinallyFlow.antecedent = currentFlow; + flowNodeCreated(afterFinallyFlow); preFinallyFlow.lock = afterFinallyFlow; currentFlow = afterFinallyFlow; } @@ -1986,7 +2027,7 @@ namespace ts { const host = getJSDocHost(typeAlias); container = findAncestor(host.parent, n => !!(getContainerFlags(n) & ContainerFlags.IsContainer)) || file; blockScopeContainer = getEnclosingBlockScopeContainer(host) || file; - currentFlow = { flags: FlowFlags.Start }; + currentFlow = createFlowStart(); parent = typeAlias; bind(typeAlias.typeExpression); const declName = getNameOfDeclaration(typeAlias); diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2f1c4f13201ec..b6ae954c6969c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -18083,7 +18083,7 @@ namespace ts { } function getFlowNodeId(flow: FlowNode): number { - if (!flow.id) { + if (!flow.id || flow.id < 0) { flow.id = nextFlowId; nextFlowId++; } diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 8703f3de8f7c2..cb71ae1fa7b21 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1159,7 +1159,7 @@ namespace ts { } } - function stableSortIndices(array: readonly T[], indices: number[], comparer: Comparer) { + export function stableSortIndices(array: readonly T[], indices: number[], comparer: Comparer) { // sort indices by value then position indices.sort((x, y) => comparer(array[x], array[y]) || compareValues(x, y)); } diff --git a/src/compiler/debug.ts b/src/compiler/debug.ts index 93b3992dbbaa0..40642840fc358 100644 --- a/src/compiler/debug.ts +++ b/src/compiler/debug.ts @@ -266,6 +266,11 @@ namespace ts { } } + Object.defineProperties(objectAllocator.getFlowNodeConstructor().prototype, { + __debugFlowFlags: { get(this: FlowNode) { return formatEnum(this.flags, (ts as any).FlowFlags, /*isFlags*/ true); } }, + __debugToString: { value(this: FlowNode) { return `\n${formatControlFlowGraph(this)}\n`; } } + }); + isDebugInfoEnabled = true; } @@ -276,7 +281,67 @@ namespace ts { console.log(formatControlFlowGraph(flowNode)); } + let nextDebugFlowId = -1; + + function getDebugFlowNodeId(f: FlowNode) { + if (!f.id) { + f.id = nextDebugFlowId; + nextDebugFlowId--; + } + return f.id; + } + export function formatControlFlowGraph(flowNode: FlowNode) { + const enum BoxCharacter { + lr = "─", + ud = "│", + dr = "╭", + dl = "╮", + ul = "╯", + ur = "╰", + udr = "├", + udl = "┤", + dlr = "┬", + ulr = "┴", + udlr = "┼", + } + + const enum Connection { + Up = 1 << 0, + Down = 1 << 1, + Left = 1 << 2, + Right = 1 << 3, + + UpDown = Up | Down, + LeftRight = Left | Right, + UpLeft = Up | Left, + UpRight = Up | Right, + DownLeft = Down | Left, + DownRight = Down | Right, + UpDownLeft = UpDown | Left, + UpDownRight = UpDown | Right, + UpLeftRight = Up | LeftRight, + DownLeftRight = Down | LeftRight, + UpDownLeftRight = UpDown | LeftRight, + + NoChildren = 1 << 4, + } + + interface FlowGraphNode { + id: number; + flowNode: FlowNode; + edges: FlowGraphEdge[]; + text: string; + lane: number; + endLane: number; + level: number; + } + + interface FlowGraphEdge { + source: FlowGraphNode; + target: FlowGraphNode; + } + const hasAntecedentFlags = FlowFlags.Assignment | FlowFlags.Condition | @@ -286,97 +351,143 @@ namespace ts { FlowFlags.PreFinally | FlowFlags.AfterFinally; - type hasAntecedent = - | FlowAssignment - | FlowCondition - | FlowSwitchClause - | FlowArrayMutation - | FlowCall - | PreFinallyFlow - | AfterFinallyFlow - ; - - const flowNodes: FlowNode[] = []; - const sharedNodes = createMap(); - const sharedIdMap = createMap(); - let nextSharedId = 1; - trackSharedNodesInGraph(flowNode); - - const deferredFlowNodes: FlowNode[] = []; - const writtenFlowNodes: FlowNode[] = []; - const writer = createTextWriter("\n"); - writeFlowNode(flowNode, /*isAntecedent*/ false); - - let hasWrittenHeader = false; - while (deferredFlowNodes.length) { - const deferred = deferredFlowNodes.splice(0, deferredFlowNodes.length); - for (const sharedNode of deferred) { - if (!hasWrittenHeader) { - writer.writeLine(); - writer.rawWrite("..."); - writer.writeLine(); - hasWrittenHeader = true; - } - if (pushIfUnique(writtenFlowNodes, sharedNode)) { - writeFlowNode(sharedNode, /*isAntecedent*/ false); - } - } + const hasNodeFlags = + FlowFlags.Start | + FlowFlags.Assignment | + FlowFlags.Call | + FlowFlags.Condition | + FlowFlags.ArrayMutation; + + const links: Record = Object.create(/*o*/ null); // eslint-disable-line no-null/no-null + const nodes: FlowGraphNode[] = []; + const edges: FlowGraphEdge[] = []; + const root = buildGraphNode(flowNode); + for (const node of nodes) { + computeLevel(node); } - return writer.getText(); + const height = computeHeight(root); + const columnWidths = computeColumnWidths(height); + computeLanes(root, 0); + return renderGraph(); - function trackSharedNodesInGraph(flowNode: FlowNode) { - if (flowNode.flags & FlowFlags.Start) { - return; - } - if (flowNode.flags & FlowFlags.Shared) { - const flowId = `${getFlowId(flowNode)}`; - const isShared = sharedNodes.get(flowId); - if (isShared === undefined) { - sharedNodes.set(flowId, false); - } - else if (isShared === false) { - sharedNodes.set(flowId, true); + function isFlowSwitchClause(f: FlowNode): f is FlowSwitchClause { + return !!(f.flags & FlowFlags.SwitchClause); + } + + function hasAntecedents(f: FlowNode): f is FlowLabel & { antecedents: FlowNode[] } { + return !!(f.flags & FlowFlags.Label) && !!(f as FlowLabel).antecedents; + } + + function hasAntecedent(f: FlowNode): f is Extract { + return !!(f.flags & hasAntecedentFlags); + } + + function hasNode(f: FlowNode): f is Extract { + return !!(f.flags & hasNodeFlags); + } + + function getChildren(node: FlowGraphNode) { + const children: FlowGraphNode[] = []; + for (const edge of node.edges) { + if (edge.source === node) { + children.push(edge.target); } } - if (flowNode.flags & FlowFlags.Label) { - forEach((flowNode as FlowLabel).antecedents, trackSharedNodesInGraph); + return children; + } + + function getParents(node: FlowGraphNode) { + const parents: FlowGraphNode[] = []; + for (const edge of node.edges) { + if (edge.target === node) { + parents.push(edge.source); + } } - else if (flowNode.flags & hasAntecedentFlags) { - trackSharedNodesInGraph((flowNode as hasAntecedent).antecedent); + return parents; + } + + function buildGraphNode(flowNode: FlowNode) { + const id = getDebugFlowNodeId(flowNode); + let graphNode = links[id]; + if (!graphNode) { + links[id] = graphNode = { id, flowNode, edges: [], text: renderFlowNode(flowNode), lane: -1, endLane: -1, level: -1 }; + nodes.push(graphNode); + if (!(flowNode.flags & FlowFlags.PreFinally)) { + if (hasAntecedents(flowNode)) { + // sort antecedents so that shallower trees come first. + const antecedents = flowNode.antecedents; + const heights = antecedents.map(measureHeight); + const indices = antecedents.map((_, i) => i); + stableSortIndices(heights, indices, (a, b) => b - a); + for (const antecedent of indices.map(i => antecedents[i])) { + buildGraphEdge(graphNode, antecedent); + } + } + else if (hasAntecedent(flowNode)) { + buildGraphEdge(graphNode, flowNode.antecedent); + } + } } + return graphNode; } - function getText(node: Node) { - return getSourceTextOfNodeFromSourceFile(getSourceFileOfNode(node), node); + function buildGraphEdge(source: FlowGraphNode, antecedent: FlowNode) { + const target = buildGraphNode(antecedent); + const edge: FlowGraphEdge = { source, target }; + edges.push(edge); + source.edges.push(edge); + target.edges.push(edge); } - function writeLines(writer: EmitTextWriter, text: string, prefix = ""): void { - const lines = text.split(/\r\n?|\n/g); - const indentation = guessIndentation(lines); - for (const lineText of lines) { - const line = indentation ? lineText.slice(indentation) : lineText; - if (line.length) { - writer.write(prefix + line); - writer.writeLine(); - } + function computeLevel(node: FlowGraphNode): number { + if (node.level !== -1) { + return node.level; + } + let level = 0; + for (const parent of getParents(node)) { + level = Math.max(level, computeLevel(parent) + 1); } + return node.level = level; } - function writeNode(node: Node | undefined) { - if (!node) return; - writer.increaseIndent(); - writeLines(writer, getText(node), "> "); - writer.decreaseIndent(); + function measureHeight(flowNode: FlowNode): number { + return hasAntecedents(flowNode) ? flowNode.antecedents.reduce((x, n) => Math.max(x, measureHeight(n)), 0) + 1 : + hasAntecedent(flowNode) ? measureHeight(flowNode.antecedent) + 1 : + 1; + } + + function computeHeight(node: FlowGraphNode): number { + let height = 0; + for (const child of getChildren(node)) { + height = Math.max(height, computeHeight(child)); + } + return height + 1; } - function getFlowId(flowNode: FlowNode) { - let index = flowNodes.indexOf(flowNode); - if (index === -1) { - index = flowNodes.length; - flowNodes.push(flowNode); + function computeColumnWidths(height: number) { + const columns: number[] = fill(Array(height), 0); + for (const node of nodes) { + columns[node.level] = Math.max(columns[node.level], node.text.length); + } + return columns; + } + + function computeLanes(node: FlowGraphNode, lane: number) { + if (node.lane === -1) { + node.lane = lane; + node.endLane = lane; + const children = getChildren(node); + for (let i = 0; i < children.length; i++) { + if (i > 0) lane++; + const child = children[i]; + computeLanes(child, lane); + if (child.endLane > node.endLane) { + lane = child.endLane; + } + } + node.endLane = lane; } - return index; } function getHeader(flags: FlowFlags) { @@ -392,144 +503,151 @@ namespace ts { if (flags & FlowFlags.PreFinally) return "PreFinally"; if (flags & FlowFlags.AfterFinally) return "AfterFinally"; if (flags & FlowFlags.Unreachable) return "Unreachable"; - return fail(); + throw new Error(); + } + + function getNodeText(node: Node) { + const sourceFile = getSourceFileOfNode(node); + return getSourceTextOfNodeFromSourceFile(sourceFile, node, /*includeTrivia*/ false); } - function writeHeader(flowNode: FlowNode, isAntecedent: boolean) { - writer.write("- "); - const flowId = `${getFlowId(flowNode)}`; - if (sharedNodes.get(flowId)) { - let sharedId = sharedIdMap.get(flowId); - if (!sharedId) { - sharedIdMap.set(flowId, sharedId = nextSharedId); - nextSharedId++; + function renderFlowNode(flowNode: FlowNode) { + let text = getHeader(flowNode.flags); + if (hasNode(flowNode)) { + if (flowNode.node) { + text += ` (${getNodeText(flowNode.node)})`; } - writer.write(`(#${sharedId}) `); } - writer.write(getHeader(flowNode.flags)); - if (isAntecedent && sharedNodes.get(flowId)) { - writer.write("..."); - } - else { - const attributes: string[] = []; - if (flowNode.flags & FlowFlags.PreFinally) { - if ((flowNode as PreFinallyFlow).lock.locked) { - attributes.push("locked"); + else if (isFlowSwitchClause(flowNode)) { + const clauses: string[] = []; + for (let i = flowNode.clauseStart; i < flowNode.clauseEnd; i++) { + const clause = flowNode.switchStatement.caseBlock.clauses[i]; + if (isDefaultClause(clause)) { + clauses.push("default"); } - } - else if (flowNode.flags & FlowFlags.AfterFinally) { - if ((flowNode as AfterFinallyFlow).locked) { - attributes.push("locked"); + else { + clauses.push(getNodeText(clause.expression)); } } - if (!(flowNode.flags & FlowFlags.Referenced)) { - attributes.push("unreferenced"); + text += ` (${clauses.join(", ")})`; + } + return text; + } + + function renderGraph() { + const columnCount = columnWidths.length; + const laneCount = nodes.reduce((x, n) => Math.max(x, n.lane), 0) + 1; + const lanes: string[] = fill(Array(laneCount), ""); + const grid: (FlowGraphNode | undefined)[][] = columnWidths.map(() => Array(laneCount)); + const connectors: Connection[][] = columnWidths.map(() => fill(Array(laneCount), 0)); + + // build connectors + for (const node of nodes) { + grid[node.level][node.lane] = node; + const children = getChildren(node); + for (let i = 0; i < children.length; i++) { + const child = children[i]; + let connector: Connection = Connection.Right; + if (child.lane === node.lane) connector |= Connection.Left; + if (i > 0) connector |= Connection.Up; + if (i < children.length - 1) connector |= Connection.Down; + connectors[node.level][child.lane] |= connector; } - if (flowNode.flags & FlowFlags.Cached) { - attributes.push("cached"); + if (children.length === 0) { + connectors[node.level][node.lane] |= Connection.NoChildren; } - if (some(attributes)) { - writer.write(`[${attributes.join(", ")}]`); + const parents = getParents(node); + for (let i = 0; i < parents.length; i++) { + const parent = parents[i]; + let connector: Connection = Connection.Left; + if (i > 0) connector |= Connection.Up; + if (i < parents.length - 1) connector |= Connection.Down; + connectors[node.level - 1][parent.lane] |= connector; } } - writer.writeLine(); - } - function writeAntecedent(antecedent: FlowNode) { - writer.increaseIndent(); - writeFlowNode(antecedent, /*isAntecedent*/ true); - writer.decreaseIndent(); - } - - function writeAntecedents(antecedents: FlowNode[] | undefined) { - if (antecedents) { - writer.increaseIndent(); - for (const antecedent of antecedents) { - writeFlowNode(antecedent, /*isAntecedent*/ true); + // fill in missing connectors + for (let column = 0; column < columnCount; column++) { + for (let lane = 0; lane < laneCount; lane++) { + const left = column > 0 ? connectors[column - 1][lane] : 0; + const above = lane > 0 ? connectors[column][lane - 1] : 0; + let connector = connectors[column][lane]; + if (!connector) { + if (left & Connection.Right) connector |= Connection.LeftRight; + if (above & Connection.Down) connector |= Connection.UpDown; + connectors[column][lane] = connector; + } } - writer.decreaseIndent(); } - } - function writeFlowNode(flowNode: FlowNode, isAntecedent: boolean) { - writeHeader(flowNode, isAntecedent); - const flowId = `${getFlowId(flowNode)}`; - if (sharedNodes.get(flowId) && isAntecedent) { - pushIfUnique(deferredFlowNodes, flowNode); - } - else if (flowNode.flags & FlowFlags.Start) { - writeStart(flowNode as FlowStart); - } - else if (flowNode.flags & FlowFlags.Label) { - writeLabel(flowNode as FlowLabel); - } - else if (flowNode.flags & FlowFlags.Assignment) { - writeAssignment(flowNode as FlowAssignment); - } - else if (flowNode.flags & FlowFlags.Condition) { - writeCondition(flowNode as FlowCondition); - } - else if (flowNode.flags & FlowFlags.SwitchClause) { - writeSwitchClause(flowNode as FlowSwitchClause); - } - else if (flowNode.flags & FlowFlags.ArrayMutation) { - writeArrayMutation(flowNode as FlowArrayMutation); - } - else if (flowNode.flags & FlowFlags.Call) { - writeCall(flowNode as FlowCall); - } - else if (flowNode.flags & FlowFlags.PreFinally) { - writePreFinally(flowNode as PreFinallyFlow); - } - else if (flowNode.flags & FlowFlags.AfterFinally) { - writeAfterFinally(flowNode as AfterFinallyFlow); - } - else { - assert(!!(flowNode.flags & FlowFlags.Unreachable)); + for (let column = 0; column < columnCount; column++) { + for (let lane = 0; lane < lanes.length; lane++) { + const connector = connectors[column][lane]; + const fill = connector & Connection.Left ? BoxCharacter.lr : " "; + const node = grid[column][lane]; + if (!node) { + if (column < columnCount - 1) { + writeLane(lane, repeat(fill, columnWidths[column] + 1)); + } + } + else { + writeLane(lane, node.text); + if (column < columnCount - 1) { + writeLane(lane, " "); + writeLane(lane, repeat(fill, columnWidths[column] - node.text.length)); + } + } + writeLane(lane, getBoxCharacter(connector)); + writeLane(lane, connector & Connection.Right && column < columnCount - 1 && !grid[column + 1][lane] ? BoxCharacter.lr : " "); + } } - } - - function writeStart(flowNode: FlowStart) { - writeNode(flowNode.node); - } - - function writeLabel(flowNode: FlowLabel) { - writeAntecedents(flowNode.antecedents); - } - - function writeAssignment(flowNode: FlowAssignment) { - writeNode(flowNode.node); - writeAntecedent(flowNode.antecedent); - } - function writeCondition(flowNode: FlowCondition) { - writeNode(flowNode.node); - writeAntecedent(flowNode.antecedent); - } + return lanes.join("\n"); - function writeSwitchClause(flowNode: FlowSwitchClause) { - for (let i = flowNode.clauseStart; i < flowNode.clauseEnd; i++) { - writeNode(flowNode.switchStatement.caseBlock.clauses[i]); + function writeLane(lane: number, text: string) { + lanes[lane] += text; } - writeAntecedent(flowNode.antecedent); } - function writeArrayMutation(flowNode: FlowArrayMutation) { - writeNode(flowNode.node); - writeAntecedent(flowNode.antecedent); + function getBoxCharacter(connector: Connection) { + switch (connector) { + case Connection.UpDown: return BoxCharacter.ud; + case Connection.LeftRight: return BoxCharacter.lr; + case Connection.UpLeft: return BoxCharacter.ul; + case Connection.UpRight: return BoxCharacter.ur; + case Connection.DownLeft: return BoxCharacter.dl; + case Connection.DownRight: return BoxCharacter.dr; + case Connection.UpDownLeft: return BoxCharacter.udl; + case Connection.UpDownRight: return BoxCharacter.udr; + case Connection.UpLeftRight: return BoxCharacter.ulr; + case Connection.DownLeftRight: return BoxCharacter.dlr; + case Connection.UpDownLeftRight: return BoxCharacter.udlr; + } + return " "; } - function writeCall(flowNode: FlowCall) { - writeNode(flowNode.node); - writeAntecedent(flowNode.antecedent); + function fill(array: T[], value: T) { + if (array.fill) { + array.fill(value); + } + else { + for (let i = 0; i < array.length; i++) { + array[i] = value; + } + } + return array; } - function writePreFinally(flowNode: PreFinallyFlow) { - writeAntecedent(flowNode.antecedent); - } + function repeat(ch: string, length: number) { + if (ch.repeat) { + return length > 0 ? ch.repeat(length) : ""; + } - function writeAfterFinally(flowNode: AfterFinallyFlow) { - writeAntecedent(flowNode.antecedent); + let s = ""; + while (s.length < length) { + s += ch; + } + return s; } } } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 88aaf144c51d3..b9279f38e31eb 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -7209,6 +7209,7 @@ namespace ts { getTypeConstructor(): new (checker: TypeChecker, flags: TypeFlags) => Type; getSignatureConstructor(): new (checker: TypeChecker) => Signature; getSourceMapSourceConstructor(): new (fileName: string, text: string, skipTrivia?: (pos: number) => number) => SourceMapSource; + getFlowNodeConstructor(): new (flags: FlowFlags) => FlowNodeBase; } function Symbol(this: Symbol, flags: SymbolFlags, name: __String) { @@ -7248,6 +7249,11 @@ namespace ts { this.skipTrivia = skipTrivia || (pos => pos); } + function FlowNode(this: FlowNodeBase, flags: FlowFlags) { + this.flags = flags; + this.id = 0; + } + // eslint-disable-next-line prefer-const export let objectAllocator: ObjectAllocator = { getNodeConstructor: () => Node, @@ -7258,6 +7264,7 @@ namespace ts { getTypeConstructor: () => Type, getSignatureConstructor: () => Signature, getSourceMapSourceConstructor: () => SourceMapSource, + getFlowNodeConstructor: () => FlowNode, }; export function formatStringFromArgs(text: string, args: ArrayLike, baseIndex = 0): string { diff --git a/src/services/services.ts b/src/services/services.ts index 90996d336d051..69b1c67ffd8ef 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -792,6 +792,11 @@ namespace ts { } } + class FlowNodeObject implements FlowNodeBase { + id = 0; + constructor(public flags: FlowFlags) { } + } + function getServicesObjectAllocator(): ObjectAllocator { return { getNodeConstructor: () => NodeObject, @@ -803,6 +808,7 @@ namespace ts { getTypeConstructor: () => TypeObject, getSignatureConstructor: () => SignatureObject, getSourceMapSourceConstructor: () => SourceMapSourceObject, + getFlowNodeConstructor: () => FlowNodeObject }; } From c2070bead0dd306e92b1bfb663c316bdbbf45589 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Mon, 30 Sep 2019 11:36:34 -0700 Subject: [PATCH 19/19] Revert Debug.formatControlFlowGraph helper --- src/compiler/binder.ts | 69 ++----- src/compiler/core.ts | 2 +- src/compiler/debug.ts | 371 -------------------------------------- src/compiler/utilities.ts | 7 - src/services/services.ts | 6 - 5 files changed, 15 insertions(+), 440 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index c56efa0562e78..89a475bfaf771 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -209,11 +209,10 @@ namespace ts { let symbolCount = 0; let Symbol: new (flags: SymbolFlags, name: __String) => Symbol; - let FlowNode: new (flags: FlowFlags) => FlowNodeBase; let classifiableNames: UnderscoreEscapedMap; - let unreachableFlow: FlowNode; - let reportedUnreachableFlow: FlowNode; + const unreachableFlow: FlowNode = { flags: FlowFlags.Unreachable }; + const reportedUnreachableFlow: FlowNode = { flags: FlowFlags.Unreachable }; // state used to aggregate transform flags during bind. let subtreeTransformFlags: TransformFlags = TransformFlags.None; @@ -228,17 +227,6 @@ namespace ts { return createDiagnosticForNodeInSourceFile(getSourceFileOfNode(node) || file, node, message, arg0, arg1, arg2); } - function initializeBinder() { - const symbolConstructor = objectAllocator.getSymbolConstructor(); - const flowNodeConstructor = objectAllocator.getFlowNodeConstructor(); - if (Symbol !== symbolConstructor || FlowNode !== flowNodeConstructor) { - Symbol = symbolConstructor; - FlowNode = flowNodeConstructor; - unreachableFlow = new FlowNode(FlowFlags.Unreachable); - reportedUnreachableFlow = new FlowNode(FlowFlags.Unreachable); - } - } - function bindSourceFile(f: SourceFile, opts: CompilerOptions) { file = f; options = opts; @@ -248,7 +236,7 @@ namespace ts { symbolCount = 0; skipTransformFlagAggregation = file.isDeclarationFile; - initializeBinder(); + Symbol = objectAllocator.getSymbolConstructor(); if (!file.locals) { bind(file); @@ -635,7 +623,7 @@ namespace ts { // A non-async, non-generator IIFE is considered part of the containing control flow. Return statements behave // similarly to break statements that exit to a label just past the statement body. if (!isIIFE) { - currentFlow = createFlowStart(); + currentFlow = { flags: FlowFlags.Start }; if (containerFlags & (ContainerFlags.IsFunctionExpression | ContainerFlags.IsObjectLiteralOrClassExpressionMethod)) { currentFlow.node = node; } @@ -929,15 +917,11 @@ namespace ts { } function createBranchLabel(): FlowLabel { - const flow = new FlowNode(FlowFlags.BranchLabel) as FlowLabel; - flow.antecedents = undefined; - return flow; + return { flags: FlowFlags.BranchLabel, antecedents: undefined }; } function createLoopLabel(): FlowLabel { - const flow = new FlowNode(FlowFlags.LoopLabel) as FlowLabel; - flow.antecedents = undefined; - return flow; + return { flags: FlowFlags.LoopLabel, antecedents: undefined }; } function setFlowNodeReferenced(flow: FlowNode) { @@ -952,10 +936,6 @@ namespace ts { } } - function createFlowStart(): FlowStart { - return new FlowNode(FlowFlags.Start) as FlowStart; - } - function createFlowCondition(flags: FlowFlags, antecedent: FlowNode, expression: Expression | undefined): FlowNode { if (antecedent.flags & FlowFlags.Unreachable) { return antecedent; @@ -973,10 +953,7 @@ namespace ts { return antecedent; } setFlowNodeReferenced(antecedent); - const flow = new FlowNode(flags) as FlowCondition; - flow.antecedent = antecedent; - flow.node = expression; - return flowNodeCreated(flow); + return flowNodeCreated({ flags, antecedent, node: expression }); } function createFlowSwitchClause(antecedent: FlowNode, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number): FlowNode { @@ -984,36 +961,22 @@ namespace ts { return antecedent; } setFlowNodeReferenced(antecedent); - const flow = new FlowNode(FlowFlags.SwitchClause) as FlowSwitchClause; - flow.antecedent = antecedent; - flow.switchStatement = switchStatement; - flow.clauseStart = clauseStart; - flow.clauseEnd = clauseEnd; - return flowNodeCreated(flow); + return flowNodeCreated({ flags: FlowFlags.SwitchClause, antecedent, switchStatement, clauseStart, clauseEnd }); } function createFlowAssignment(antecedent: FlowNode, node: Expression | VariableDeclaration | BindingElement): FlowNode { setFlowNodeReferenced(antecedent); - const flow = new FlowNode(FlowFlags.Assignment) as FlowAssignment; - flow.antecedent = antecedent; - flow.node = node; - return flowNodeCreated(flow); + return flowNodeCreated({ flags: FlowFlags.Assignment, antecedent, node }); } function createFlowCall(antecedent: FlowNode, node: CallExpression): FlowNode { setFlowNodeReferenced(antecedent); - const flow = new FlowNode(FlowFlags.Call) as FlowCall; - flow.antecedent = antecedent; - flow.node = node; - return flowNodeCreated(flow); + return flowNodeCreated({ flags: FlowFlags.Call, antecedent, node }); } function createFlowArrayMutation(antecedent: FlowNode, node: CallExpression | BinaryExpression): FlowNode { setFlowNodeReferenced(antecedent); - const flow = new FlowNode(FlowFlags.ArrayMutation) as FlowArrayMutation; - flow.antecedent = antecedent; - flow.node = node; - return flowNodeCreated(flow); + return flowNodeCreated({ flags: FlowFlags.ArrayMutation, antecedent, node }); } function finishFlowLabel(flow: FlowLabel): FlowNode { @@ -1296,9 +1259,7 @@ namespace ts { // // extra edges that we inject allows to control this behavior // if when walking the flow we step on post-finally edge - we can mark matching pre-finally edge as locked so it will be skipped. - const preFinallyFlow = new FlowNode(FlowFlags.PreFinally) as PreFinallyFlow; - preFinallyFlow.antecedent = preFinallyPrior; - preFinallyFlow.lock = {}; + const preFinallyFlow: PreFinallyFlow = { flags: FlowFlags.PreFinally, antecedent: preFinallyPrior, lock: {} }; addAntecedent(preFinallyLabel, preFinallyFlow); currentFlow = finishFlowLabel(preFinallyLabel); @@ -1317,9 +1278,7 @@ namespace ts { } } if (!(currentFlow.flags & FlowFlags.Unreachable)) { - const afterFinallyFlow = new FlowNode(FlowFlags.AfterFinally) as AfterFinallyFlow; - afterFinallyFlow.antecedent = currentFlow; - flowNodeCreated(afterFinallyFlow); + const afterFinallyFlow: AfterFinallyFlow = flowNodeCreated({ flags: FlowFlags.AfterFinally, antecedent: currentFlow }); preFinallyFlow.lock = afterFinallyFlow; currentFlow = afterFinallyFlow; } @@ -2027,7 +1986,7 @@ namespace ts { const host = getJSDocHost(typeAlias); container = findAncestor(host.parent, n => !!(getContainerFlags(n) & ContainerFlags.IsContainer)) || file; blockScopeContainer = getEnclosingBlockScopeContainer(host) || file; - currentFlow = createFlowStart(); + currentFlow = { flags: FlowFlags.Start }; parent = typeAlias; bind(typeAlias.typeExpression); const declName = getNameOfDeclaration(typeAlias); diff --git a/src/compiler/core.ts b/src/compiler/core.ts index cb71ae1fa7b21..8703f3de8f7c2 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1159,7 +1159,7 @@ namespace ts { } } - export function stableSortIndices(array: readonly T[], indices: number[], comparer: Comparer) { + function stableSortIndices(array: readonly T[], indices: number[], comparer: Comparer) { // sort indices by value then position indices.sort((x, y) => comparer(array[x], array[y]) || compareValues(x, y)); } diff --git a/src/compiler/debug.ts b/src/compiler/debug.ts index 6a5e45dbc1324..09d230f24e9e8 100644 --- a/src/compiler/debug.ts +++ b/src/compiler/debug.ts @@ -266,379 +266,8 @@ namespace ts { } } - Object.defineProperties(objectAllocator.getFlowNodeConstructor().prototype, { - __debugFlowFlags: { get(this: FlowNode) { return formatEnum(this.flags, (ts as any).FlowFlags, /*isFlags*/ true); } }, - __debugToString: { value(this: FlowNode) { return `\n${formatControlFlowGraph(this)}\n`; } } - }); - isDebugInfoEnabled = true; } - /** - * Prints a control-flow graph for debugging purposes. - */ - export function printControlFlowGraph(flowNode: FlowNode) { - console.log(formatControlFlowGraph(flowNode)); - } - - let nextDebugFlowId = -1; - - function getDebugFlowNodeId(f: FlowNode) { - if (!f.id) { - f.id = nextDebugFlowId; - nextDebugFlowId--; - } - return f.id; - } - - export function formatControlFlowGraph(flowNode: FlowNode) { - const enum BoxCharacter { - lr = "─", - ud = "│", - dr = "╭", - dl = "╮", - ul = "╯", - ur = "╰", - udr = "├", - udl = "┤", - dlr = "┬", - ulr = "┴", - udlr = "╫", - } - - const enum Connection { - Up = 1 << 0, - Down = 1 << 1, - Left = 1 << 2, - Right = 1 << 3, - - UpDown = Up | Down, - LeftRight = Left | Right, - UpLeft = Up | Left, - UpRight = Up | Right, - DownLeft = Down | Left, - DownRight = Down | Right, - UpDownLeft = UpDown | Left, - UpDownRight = UpDown | Right, - UpLeftRight = Up | LeftRight, - DownLeftRight = Down | LeftRight, - UpDownLeftRight = UpDown | LeftRight, - - NoChildren = 1 << 4, - } - - interface FlowGraphNode { - id: number; - flowNode: FlowNode; - edges: FlowGraphEdge[]; - text: string; - lane: number; - endLane: number; - level: number; - } - - interface FlowGraphEdge { - source: FlowGraphNode; - target: FlowGraphNode; - } - - const hasAntecedentFlags = - FlowFlags.Assignment | - FlowFlags.Condition | - FlowFlags.SwitchClause | - FlowFlags.ArrayMutation | - FlowFlags.Call | - FlowFlags.PreFinally | - FlowFlags.AfterFinally; - - const hasNodeFlags = - FlowFlags.Start | - FlowFlags.Assignment | - FlowFlags.Call | - FlowFlags.Condition | - FlowFlags.ArrayMutation; - - const links: Record = Object.create(/*o*/ null); // eslint-disable-line no-null/no-null - const nodes: FlowGraphNode[] = []; - const edges: FlowGraphEdge[] = []; - const root = buildGraphNode(flowNode); - for (const node of nodes) { - computeLevel(node); - } - - const height = computeHeight(root); - const columnWidths = computeColumnWidths(height); - computeLanes(root, 0); - return renderGraph(); - - function isFlowSwitchClause(f: FlowNode): f is FlowSwitchClause { - return !!(f.flags & FlowFlags.SwitchClause); - } - - function hasAntecedents(f: FlowNode): f is FlowLabel & { antecedents: FlowNode[] } { - return !!(f.flags & FlowFlags.Label) && !!(f as FlowLabel).antecedents; - } - - function hasAntecedent(f: FlowNode): f is Extract { - return !!(f.flags & hasAntecedentFlags); - } - - function hasNode(f: FlowNode): f is Extract { - return !!(f.flags & hasNodeFlags); - } - - function getChildren(node: FlowGraphNode) { - const children: FlowGraphNode[] = []; - for (const edge of node.edges) { - if (edge.source === node) { - children.push(edge.target); - } - } - return children; - } - - function getParents(node: FlowGraphNode) { - const parents: FlowGraphNode[] = []; - for (const edge of node.edges) { - if (edge.target === node) { - parents.push(edge.source); - } - } - return parents; - } - - function buildGraphNode(flowNode: FlowNode) { - const id = getDebugFlowNodeId(flowNode); - let graphNode = links[id]; - if (!graphNode) { - links[id] = graphNode = { id, flowNode, edges: [], text: renderFlowNode(flowNode), lane: -1, endLane: -1, level: -1 }; - nodes.push(graphNode); - if (!(flowNode.flags & FlowFlags.PreFinally)) { - if (hasAntecedents(flowNode)) { - - for (const antecedent of flowNode.antecedents) { - buildGraphEdge(graphNode, antecedent); - } - } - else if (hasAntecedent(flowNode)) { - buildGraphEdge(graphNode, flowNode.antecedent); - } - } - } - return graphNode; - } - - function buildGraphEdge(source: FlowGraphNode, antecedent: FlowNode) { - const target = buildGraphNode(antecedent); - const edge: FlowGraphEdge = { source, target }; - edges.push(edge); - source.edges.push(edge); - target.edges.push(edge); - } - - function computeLevel(node: FlowGraphNode): number { - if (node.level !== -1) { - return node.level; - } - let level = 0; - for (const parent of getParents(node)) { - level = Math.max(level, computeLevel(parent) + 1); - } - return node.level = level; - } - - function computeHeight(node: FlowGraphNode): number { - let height = 0; - for (const child of getChildren(node)) { - height = Math.max(height, computeHeight(child)); - } - return height + 1; - } - - function computeColumnWidths(height: number) { - const columns: number[] = fill(Array(height), 0); - for (const node of nodes) { - columns[node.level] = Math.max(columns[node.level], node.text.length); - } - return columns; - } - - function computeLanes(node: FlowGraphNode, lane: number) { - if (node.lane === -1) { - node.lane = lane; - node.endLane = lane; - const children = getChildren(node); - for (let i = 0; i < children.length; i++) { - if (i > 0) lane++; - const child = children[i]; - computeLanes(child, lane); - if (child.endLane > node.endLane) { - lane = child.endLane; - } - } - node.endLane = lane; - } - } - - function getHeader(flags: FlowFlags) { - if (flags & FlowFlags.Start) return "Start"; - if (flags & FlowFlags.BranchLabel) return "Branch"; - if (flags & FlowFlags.LoopLabel) return "Loop"; - if (flags & FlowFlags.Assignment) return "Assignment"; - if (flags & FlowFlags.TrueCondition) return "True"; - if (flags & FlowFlags.FalseCondition) return "False"; - if (flags & FlowFlags.SwitchClause) return "SwitchClause"; - if (flags & FlowFlags.ArrayMutation) return "ArrayMutation"; - if (flags & FlowFlags.Call) return "Call"; - if (flags & FlowFlags.PreFinally) return "PreFinally"; - if (flags & FlowFlags.AfterFinally) return "AfterFinally"; - if (flags & FlowFlags.Unreachable) return "Unreachable"; - throw new Error(); - } - - function getNodeText(node: Node) { - const sourceFile = getSourceFileOfNode(node); - return getSourceTextOfNodeFromSourceFile(sourceFile, node, /*includeTrivia*/ false); - } - - function renderFlowNode(flowNode: FlowNode) { - let text = getHeader(flowNode.flags); - if (hasNode(flowNode)) { - if (flowNode.node) { - text += ` (${getNodeText(flowNode.node)})`; - } - } - else if (isFlowSwitchClause(flowNode)) { - const clauses: string[] = []; - for (let i = flowNode.clauseStart; i < flowNode.clauseEnd; i++) { - const clause = flowNode.switchStatement.caseBlock.clauses[i]; - if (isDefaultClause(clause)) { - clauses.push("default"); - } - else { - clauses.push(getNodeText(clause.expression)); - } - } - text += ` (${clauses.join(", ")})`; - } - return text; - } - - function renderGraph() { - const columnCount = columnWidths.length; - const laneCount = nodes.reduce((x, n) => Math.max(x, n.lane), 0) + 1; - const lanes: string[] = fill(Array(laneCount), ""); - const grid: (FlowGraphNode | undefined)[][] = columnWidths.map(() => Array(laneCount)); - const connectors: Connection[][] = columnWidths.map(() => fill(Array(laneCount), 0)); - - // build connectors - for (const node of nodes) { - grid[node.level][node.lane] = node; - const children = getChildren(node); - for (let i = 0; i < children.length; i++) { - const child = children[i]; - let connector: Connection = Connection.Right; - if (child.lane === node.lane) connector |= Connection.Left; - if (i > 0) connector |= Connection.Up; - if (i < children.length - 1) connector |= Connection.Down; - connectors[node.level][child.lane] |= connector; - } - if (children.length === 0) { - connectors[node.level][node.lane] |= Connection.NoChildren; - } - const parents = getParents(node); - for (let i = 0; i < parents.length; i++) { - const parent = parents[i]; - let connector: Connection = Connection.Left; - if (i > 0) connector |= Connection.Up; - if (i < parents.length - 1) connector |= Connection.Down; - connectors[node.level - 1][parent.lane] |= connector; - } - } - - // fill in missing connectors - for (let column = 0; column < columnCount; column++) { - for (let lane = 0; lane < laneCount; lane++) { - const left = column > 0 ? connectors[column - 1][lane] : 0; - const above = lane > 0 ? connectors[column][lane - 1] : 0; - let connector = connectors[column][lane]; - if (!connector) { - if (left & Connection.Right) connector |= Connection.LeftRight; - if (above & Connection.Down) connector |= Connection.UpDown; - connectors[column][lane] = connector; - } - } - } - - for (let column = 0; column < columnCount; column++) { - for (let lane = 0; lane < lanes.length; lane++) { - const connector = connectors[column][lane]; - const fill = connector & Connection.Left ? BoxCharacter.lr : " "; - const node = grid[column][lane]; - if (!node) { - if (column < columnCount - 1) { - writeLane(lane, repeat(fill, columnWidths[column] + 1)); - } - } - else { - writeLane(lane, node.text); - if (column < columnCount - 1) { - writeLane(lane, " "); - writeLane(lane, repeat(fill, columnWidths[column] - node.text.length)); - } - } - writeLane(lane, getBoxCharacter(connector)); - writeLane(lane, connector & Connection.Right && column < columnCount - 1 && !grid[column + 1][lane] ? BoxCharacter.lr : " "); - } - } - - return lanes.join("\n"); - - function writeLane(lane: number, text: string) { - lanes[lane] += text; - } - } - - function getBoxCharacter(connector: Connection) { - switch (connector) { - case Connection.UpDown: return BoxCharacter.ud; - case Connection.LeftRight: return BoxCharacter.lr; - case Connection.UpLeft: return BoxCharacter.ul; - case Connection.UpRight: return BoxCharacter.ur; - case Connection.DownLeft: return BoxCharacter.dl; - case Connection.DownRight: return BoxCharacter.dr; - case Connection.UpDownLeft: return BoxCharacter.udl; - case Connection.UpDownRight: return BoxCharacter.udr; - case Connection.UpLeftRight: return BoxCharacter.ulr; - case Connection.DownLeftRight: return BoxCharacter.dlr; - case Connection.UpDownLeftRight: return BoxCharacter.udlr; - } - return " "; - } - - function fill(array: T[], value: T) { - if (array.fill) { - array.fill(value); - } - else { - for (let i = 0; i < array.length; i++) { - array[i] = value; - } - } - return array; - } - - function repeat(ch: string, length: number) { - if (ch.repeat) { - return length > 0 ? ch.repeat(length) : ""; - } - - let s = ""; - while (s.length < length) { - s += ch; - } - return s; - } - } } } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 3dccfc6639e57..4f64fd9abee5f 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -7249,7 +7249,6 @@ namespace ts { getTypeConstructor(): new (checker: TypeChecker, flags: TypeFlags) => Type; getSignatureConstructor(): new (checker: TypeChecker) => Signature; getSourceMapSourceConstructor(): new (fileName: string, text: string, skipTrivia?: (pos: number) => number) => SourceMapSource; - getFlowNodeConstructor(): new (flags: FlowFlags) => FlowNodeBase; } function Symbol(this: Symbol, flags: SymbolFlags, name: __String) { @@ -7289,11 +7288,6 @@ namespace ts { this.skipTrivia = skipTrivia || (pos => pos); } - function FlowNode(this: FlowNodeBase, flags: FlowFlags) { - this.flags = flags; - this.id = 0; - } - // eslint-disable-next-line prefer-const export let objectAllocator: ObjectAllocator = { getNodeConstructor: () => Node, @@ -7304,7 +7298,6 @@ namespace ts { getTypeConstructor: () => Type, getSignatureConstructor: () => Signature, getSourceMapSourceConstructor: () => SourceMapSource, - getFlowNodeConstructor: () => FlowNode, }; export function formatStringFromArgs(text: string, args: ArrayLike, baseIndex = 0): string { diff --git a/src/services/services.ts b/src/services/services.ts index 6c677589e9d97..6a1c721cbba30 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -792,11 +792,6 @@ namespace ts { } } - class FlowNodeObject implements FlowNodeBase { - id = 0; - constructor(public flags: FlowFlags) { } - } - function getServicesObjectAllocator(): ObjectAllocator { return { getNodeConstructor: () => NodeObject, @@ -808,7 +803,6 @@ namespace ts { getTypeConstructor: () => TypeObject, getSignatureConstructor: () => SignatureObject, getSourceMapSourceConstructor: () => SourceMapSourceObject, - getFlowNodeConstructor: () => FlowNodeObject }; }