diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 1cc293266605b..50551759c2c6f 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1556,10 +1556,6 @@ namespace ts { } } - function isOutermostOptionalChain(node: OptionalChain) { - return !isOptionalChain(node.parent) || isOptionalChainRoot(node.parent) || node !== node.parent.expression; - } - function bindOptionalExpression(node: Expression, trueTarget: FlowLabel, falseTarget: FlowLabel) { doWithConditionalBranches(bind, node, trueTarget, falseTarget); if (!isOptionalChain(node) || isOutermostOptionalChain(node)) { diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ee12266c9eb24..ace3d5ee8cfe2 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8691,14 +8691,23 @@ namespace ts { return result; } - function getOptionalCallSignature(signature: Signature) { - return signatureIsOptionalCall(signature) ? signature : - (signature.optionalCallSignatureCache || (signature.optionalCallSignatureCache = createOptionalCallSignature(signature))); + function getOptionalCallSignature(signature: Signature, callChainFlags: SignatureFlags): Signature { + if ((signature.flags & SignatureFlags.CallChainFlags) === callChainFlags) { + return signature; + } + if (!signature.optionalCallSignatureCache) { + signature.optionalCallSignatureCache = {}; + } + const key = callChainFlags === SignatureFlags.IsInnerCallChain ? "inner" : "outer"; + return signature.optionalCallSignatureCache[key] + || (signature.optionalCallSignatureCache[key] = createOptionalCallSignature(signature, callChainFlags)); } - function createOptionalCallSignature(signature: Signature) { + function createOptionalCallSignature(signature: Signature, callChainFlags: SignatureFlags) { + Debug.assert(callChainFlags === SignatureFlags.IsInnerCallChain || callChainFlags === SignatureFlags.IsOuterCallChain, + "An optional call signature can either be for an inner call chain or an outer call chain, but not both."); const result = cloneSignature(signature); - result.flags |= SignatureFlags.IsOptionalCall; + result.flags |= callChainFlags; return result; } @@ -10313,9 +10322,12 @@ namespace ts { signature.unionSignatures ? getUnionType(map(signature.unionSignatures, getReturnTypeOfSignature), UnionReduction.Subtype) : getReturnTypeFromAnnotation(signature.declaration!) || (nodeIsMissing((signature.declaration).body) ? anyType : getReturnTypeFromBody(signature.declaration)); - if (signatureIsOptionalCall(signature)) { + if (signature.flags & SignatureFlags.IsInnerCallChain) { type = addOptionalTypeMarker(type); } + else if (signature.flags & SignatureFlags.IsOuterCallChain) { + type = getOptionalType(type); + } if (!popTypeResolution()) { if (signature.declaration) { const typeNode = getEffectiveReturnTypeNode(signature.declaration); @@ -16767,8 +16779,8 @@ namespace ts { return strictNullChecks ? filterType(type, isNotOptionalTypeMarker) : type; } - function propagateOptionalTypeMarker(type: Type, wasOptional: boolean) { - return wasOptional ? addOptionalTypeMarker(type) : type; + function propagateOptionalTypeMarker(type: Type, node: OptionalChain, wasOptional: boolean) { + return wasOptional ? isOutermostOptionalChain(node) ? getOptionalType(type) : addOptionalTypeMarker(type) : type; } function getOptionalExpressionType(exprType: Type, expression: Expression) { @@ -22835,7 +22847,7 @@ namespace ts { function checkPropertyAccessChain(node: PropertyAccessChain) { const leftType = checkExpression(node.expression); const nonOptionalType = getOptionalExpressionType(leftType, node.expression); - return propagateOptionalTypeMarker(checkPropertyAccessExpressionOrQualifiedName(node, node.expression, checkNonNullType(nonOptionalType, node.expression), node.name), nonOptionalType !== leftType); + return propagateOptionalTypeMarker(checkPropertyAccessExpressionOrQualifiedName(node, node.expression, checkNonNullType(nonOptionalType, node.expression), node.name), node, nonOptionalType !== leftType); } function checkQualifiedName(node: QualifiedName) { @@ -23267,7 +23279,7 @@ namespace ts { function checkElementAccessChain(node: ElementAccessChain) { const exprType = checkExpression(node.expression); const nonOptionalType = getOptionalExpressionType(exprType, node.expression); - return propagateOptionalTypeMarker(checkElementAccessExpression(node, checkNonNullType(nonOptionalType, node.expression)), nonOptionalType !== exprType); + return propagateOptionalTypeMarker(checkElementAccessExpression(node, checkNonNullType(nonOptionalType, node.expression)), node, nonOptionalType !== exprType); } function checkElementAccessExpression(node: ElementAccessExpression, exprType: Type): Type { @@ -23372,7 +23384,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: readonly Signature[], result: Signature[], isOptionalCall: boolean): void { + function reorderCandidates(signatures: readonly Signature[], result: Signature[], callChainFlags: SignatureFlags): void { let lastParent: Node | undefined; let lastSymbol: Symbol | undefined; let cutoffIndex = 0; @@ -23414,7 +23426,7 @@ namespace ts { spliceIndex = index; } - result.splice(spliceIndex, 0, isOptionalCall ? getOptionalCallSignature(signature) : signature); + result.splice(spliceIndex, 0, callChainFlags ? getOptionalCallSignature(signature, callChainFlags) : signature); } } @@ -24080,7 +24092,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: readonly Signature[], candidatesOutArray: Signature[] | undefined, checkMode: CheckMode, isOptionalCall: boolean, fallbackError?: DiagnosticMessage): Signature { + function resolveCall(node: CallLikeExpression, signatures: readonly Signature[], candidatesOutArray: Signature[] | undefined, checkMode: CheckMode, callChainFlags: SignatureFlags, fallbackError?: DiagnosticMessage): Signature { const isTaggedTemplate = node.kind === SyntaxKind.TaggedTemplateExpression; const isDecorator = node.kind === SyntaxKind.Decorator; const isJsxOpeningOrSelfClosingElement = isJsxOpeningLikeElement(node); @@ -24099,7 +24111,7 @@ namespace ts { const candidates = candidatesOutArray || []; // reorderCandidates fills up the candidates array directly - reorderCandidates(signatures, candidates, isOptionalCall); + reorderCandidates(signatures, candidates, callChainFlags); if (!candidates.length) { if (reportErrors) { diagnostics.add(getDiagnosticForCallNode(node, Diagnostics.Call_target_does_not_contain_any_signatures)); @@ -24486,22 +24498,25 @@ namespace ts { const baseTypeNode = getEffectiveBaseTypeNode(getContainingClass(node)!); if (baseTypeNode) { const baseConstructors = getInstantiatedConstructorsForTypeArguments(superType, baseTypeNode.typeArguments, baseTypeNode); - return resolveCall(node, baseConstructors, candidatesOutArray, checkMode, /*isOptional*/ false); + return resolveCall(node, baseConstructors, candidatesOutArray, checkMode, SignatureFlags.None); } } return resolveUntypedCall(node); } - let isOptional: boolean; + let callChainFlags: SignatureFlags; let funcType = checkExpression(node.expression); if (isCallChain(node)) { const nonOptionalType = getOptionalExpressionType(funcType, node.expression); - isOptional = nonOptionalType !== funcType; + callChainFlags = nonOptionalType === funcType ? SignatureFlags.None : + isOutermostOptionalChain(node) ? SignatureFlags.IsOuterCallChain : + SignatureFlags.IsInnerCallChain; funcType = nonOptionalType; } else { - isOptional = false; + callChainFlags = SignatureFlags.None; } + funcType = checkNonNullTypeWithReporter( funcType, node.expression, @@ -24577,7 +24592,7 @@ namespace ts { return resolveErrorCall(node); } - return resolveCall(node, callSignatures, candidatesOutArray, checkMode, isOptional); + return resolveCall(node, callSignatures, candidatesOutArray, checkMode, callChainFlags); } function isGenericFunctionReturningFunction(signature: Signature) { @@ -24648,7 +24663,7 @@ namespace ts { return resolveErrorCall(node); } - return resolveCall(node, constructSignatures, candidatesOutArray, checkMode, /*isOptional*/ false); + return resolveCall(node, constructSignatures, candidatesOutArray, checkMode, SignatureFlags.None); } // If expressionType's apparent type is an object type with no construct signatures but @@ -24657,7 +24672,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, /*isOptional*/ false); + const signature = resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None); 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); @@ -24872,7 +24887,7 @@ namespace ts { return resolveErrorCall(node); } - return resolveCall(node, callSignatures, candidatesOutArray, checkMode, /*isOptional*/ false); + return resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None); } /** @@ -24935,7 +24950,7 @@ namespace ts { return resolveErrorCall(node); } - return resolveCall(node, callSignatures, candidatesOutArray, checkMode, /*isOptional*/ false, headMessage); + return resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None, headMessage); } function createSignatureForJSXIntrinsic(node: JsxOpeningLikeElement, result: Type): Signature { @@ -24987,7 +25002,7 @@ namespace ts { return resolveErrorCall(node); } - return resolveCall(node, signatures, candidatesOutArray, checkMode, /*isOptional*/ false); + return resolveCall(node, signatures, candidatesOutArray, checkMode, SignatureFlags.None); } /** @@ -27460,6 +27475,20 @@ namespace ts { } } + function getReturnTypeOfSingleNonGenericCallSignature(funcType: Type) { + const signature = getSingleCallSignature(funcType); + if (signature && !signature.typeParameters) { + return getReturnTypeOfSignature(signature); + } + } + + function getReturnTypeOfSingleNonGenericSignatureOfCallChain(expr: CallChain) { + const funcType = checkExpression(expr.expression); + const nonOptionalType = getOptionalExpressionType(funcType, expr.expression); + const returnType = getReturnTypeOfSingleNonGenericCallSignature(funcType); + return returnType && propagateOptionalTypeMarker(returnType, expr, nonOptionalType !== funcType); + } + /** * Returns the type of an expression. Unlike checkExpression, this function is simply concerned * with computing the type and may not fully check all contained sub-expressions for errors. @@ -27471,21 +27500,10 @@ namespace ts { // Optimize for the common case of a call to a function with a single non-generic call // signature where we can just fetch the return type without checking the arguments. if (isCallExpression(expr) && expr.expression.kind !== SyntaxKind.SuperKeyword && !isRequireCall(expr, /*checkArgumentIsStringLiteralLike*/ true) && !isSymbolOrSymbolForCall(expr)) { - let isOptional: boolean; - let funcType: Type; - if (isCallChain(expr)) { - funcType = checkExpression(expr.expression); - const nonOptionalType = getOptionalExpressionType(funcType, expr.expression); - isOptional = funcType !== nonOptionalType; - funcType = checkNonNullType(nonOptionalType, expr.expression); - } - else { - isOptional = false; - funcType = checkNonNullExpression(expr.expression); - } - const signature = getSingleCallSignature(funcType); - if (signature && !signature.typeParameters) { - return propagateOptionalTypeMarker(getReturnTypeOfSignature(signature), isOptional); + const type = isCallChain(expr) ? getReturnTypeOfSingleNonGenericSignatureOfCallChain(expr) : + getReturnTypeOfSingleNonGenericCallSignature(checkNonNullExpression(expr.expression)); + if (type) { + return type; } } else if (isAssertionExpression(expr) && !isConstTypeReference(expr.type)) { @@ -36198,7 +36216,4 @@ namespace ts { return !!(s.flags & SignatureFlags.HasLiteralTypes); } - export function signatureIsOptionalCall(s: Signature) { - return !!(s.flags & SignatureFlags.IsOptionalCall); - } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 25ed46dde55a9..08cc7343ccaea 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4673,14 +4673,17 @@ namespace ts { /* @internal */ export const enum SignatureFlags { None = 0, - HasRestParameter = 1 << 0, // Indicates last parameter is rest parameter - HasLiteralTypes = 1 << 1, // Indicates signature is specialized - IsOptionalCall = 1 << 2, // Indicates signature comes from a CallChain + HasRestParameter = 1 << 0, // Indicates last parameter is rest parameter + HasLiteralTypes = 1 << 1, // Indicates signature is specialized + IsInnerCallChain = 1 << 2, // Indicates signature comes from a CallChain nested in an outer OptionalChain + IsOuterCallChain = 1 << 3, // Indicates signature comes from a CallChain that is the outermost chain of an optional expression - // We do not propagate `IsOptionalCall` to instantiated signatures, as that would result in us + // We do not propagate `IsInnerCallChain` to instantiated signatures, as that would result in us // attempting to add `| undefined` on each recursive call to `getReturnTypeOfSignature` when // instantiating the return type. PropagatingFlags = HasRestParameter | HasLiteralTypes, + + CallChainFlags = IsInnerCallChain | IsOuterCallChain, } export interface Signature { @@ -4712,7 +4715,7 @@ namespace ts { /* @internal */ canonicalSignatureCache?: Signature; // Canonical version of signature (deferred) /* @internal */ - optionalCallSignatureCache?: Signature; // Optional chained call version of signature (deferred) + optionalCallSignatureCache?: { inner?: Signature, outer?: Signature }; // Optional chained call version of signature (deferred) /* @internal */ isolatedSignatureType?: ObjectType; // A manufactured type that just contains the signature for purposes of signature comparison /* @internal */ diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 4bff443754b3d..7ee5dcd8278e0 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -5947,6 +5947,11 @@ namespace ts { || kind === SyntaxKind.CallExpression); } + /* @internal */ + export function isOptionalChainRoot(node: Node): node is OptionalChainRoot { + return isOptionalChain(node) && !!node.questionDotToken; + } + /** * Determines whether a node is the expression preceding an optional chain (i.e. `a` in `a?.b`). */ @@ -5955,6 +5960,23 @@ namespace ts { return isOptionalChainRoot(node.parent) && node.parent.expression === node; } + /** + * Determines whether a node is the outermost `OptionalChain` in an ECMAScript `OptionalExpression`: + * + * 1. For `a?.b.c`, the outermost chain is `a?.b.c` (`c` is the end of the chain starting at `a?.`) + * 2. For `(a?.b.c).d`, the outermost chain is `a?.b.c` (`c` is the end of the chain starting at `a?.` since parens end the chain) + * 3. For `a?.b.c?.d`, both `a?.b.c` and `a?.b.c?.d` are outermost (`c` is the end of the chain starting at `a?.`, and `d` is + * the end of the chain starting at `c?.`) + * 4. For `a?.(b?.c).d`, both `b?.c` and `a?.(b?.c)d` are outermost (`c` is the end of the chain starting at `b`, and `d` is + * the end of the chain starting at `a?.`) + */ + /* @internal */ + export function isOutermostOptionalChain(node: OptionalChain) { + return !isOptionalChain(node.parent) // cases 1 and 2 + || isOptionalChainRoot(node.parent) // case 3 + || node !== node.parent.expression; // case 4 + } + export function isNullishCoalesce(node: Node) { return node.kind === SyntaxKind.BinaryExpression && (node).operatorToken.kind === SyntaxKind.QuestionQuestionToken; } @@ -7276,11 +7298,6 @@ namespace ts { return node.kind === SyntaxKind.GetAccessor; } - /* @internal */ - export function isOptionalChainRoot(node: Node): node is OptionalChainRoot { - return isOptionalChain(node) && !!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/callChain.3.types b/tests/baselines/reference/callChain.3.types index 7646b5418a7ee..118c6c7b1c034 100644 --- a/tests/baselines/reference/callChain.3.types +++ b/tests/baselines/reference/callChain.3.types @@ -45,9 +45,9 @@ const n4: number | undefined = a?.m?.({x: absorb()}); // likewise >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 +>{x: absorb()} : { x: number; } +>x : number +>absorb() : number >absorb : () => T // Also a test showing `!` vs `?` for good measure diff --git a/tests/baselines/reference/optionalChainingInference.js b/tests/baselines/reference/optionalChainingInference.js new file mode 100644 index 0000000000000..2f17129d70113 --- /dev/null +++ b/tests/baselines/reference/optionalChainingInference.js @@ -0,0 +1,52 @@ +//// [optionalChainingInference.ts] +// https://github.com/microsoft/TypeScript/issues/34579 +declare function unbox(box: { value: T | undefined }): T; +declare const su: string | undefined; +declare const fnu: (() => number) | undefined; +declare const osu: { prop: string } | undefined; +declare const ofnu: { prop: () => number } | undefined; + +const b1 = { value: su?.length }; +const v1: number = unbox(b1); + +const b2 = { value: su?.length as number | undefined }; +const v2: number = unbox(b2); + +const b3: { value: number | undefined } = { value: su?.length }; +const v3: number = unbox(b3); + +const b4 = { value: fnu?.() }; +const v4: number = unbox(b4); + +const b5 = { value: su?.["length"] }; +const v5: number = unbox(b5); + +const b6 = { value: osu?.prop.length }; +const v6: number = unbox(b6); + +const b7 = { value: osu?.prop["length"] }; +const v7: number = unbox(b7); + +const b8 = { value: ofnu?.prop() }; +const v8: number = unbox(b8); + + + +//// [optionalChainingInference.js] +var _a, _b, _c, _d, _e, _f, _g, _h; +var b1 = { value: (_a = su) === null || _a === void 0 ? void 0 : _a.length }; +var v1 = unbox(b1); +var b2 = { value: (_b = su) === null || _b === void 0 ? void 0 : _b.length }; +var v2 = unbox(b2); +var b3 = { value: (_c = su) === null || _c === void 0 ? void 0 : _c.length }; +var v3 = unbox(b3); +var b4 = { value: (_d = fnu) === null || _d === void 0 ? void 0 : _d() }; +var v4 = unbox(b4); +var b5 = { value: (_e = su) === null || _e === void 0 ? void 0 : _e["length"] }; +var v5 = unbox(b5); +var b6 = { value: (_f = osu) === null || _f === void 0 ? void 0 : _f.prop.length }; +var v6 = unbox(b6); +var b7 = { value: (_g = osu) === null || _g === void 0 ? void 0 : _g.prop["length"] }; +var v7 = unbox(b7); +var b8 = { value: (_h = ofnu) === null || _h === void 0 ? void 0 : _h.prop() }; +var v8 = unbox(b8); diff --git a/tests/baselines/reference/optionalChainingInference.symbols b/tests/baselines/reference/optionalChainingInference.symbols new file mode 100644 index 0000000000000..1fb3dc4cf9c1e --- /dev/null +++ b/tests/baselines/reference/optionalChainingInference.symbols @@ -0,0 +1,122 @@ +=== tests/cases/conformance/expressions/optionalChaining/optionalChainingInference.ts === +// https://github.com/microsoft/TypeScript/issues/34579 +declare function unbox(box: { value: T | undefined }): T; +>unbox : Symbol(unbox, Decl(optionalChainingInference.ts, 0, 0)) +>T : Symbol(T, Decl(optionalChainingInference.ts, 1, 23)) +>box : Symbol(box, Decl(optionalChainingInference.ts, 1, 26)) +>value : Symbol(value, Decl(optionalChainingInference.ts, 1, 32)) +>T : Symbol(T, Decl(optionalChainingInference.ts, 1, 23)) +>T : Symbol(T, Decl(optionalChainingInference.ts, 1, 23)) + +declare const su: string | undefined; +>su : Symbol(su, Decl(optionalChainingInference.ts, 2, 13)) + +declare const fnu: (() => number) | undefined; +>fnu : Symbol(fnu, Decl(optionalChainingInference.ts, 3, 13)) + +declare const osu: { prop: string } | undefined; +>osu : Symbol(osu, Decl(optionalChainingInference.ts, 4, 13)) +>prop : Symbol(prop, Decl(optionalChainingInference.ts, 4, 20)) + +declare const ofnu: { prop: () => number } | undefined; +>ofnu : Symbol(ofnu, Decl(optionalChainingInference.ts, 5, 13)) +>prop : Symbol(prop, Decl(optionalChainingInference.ts, 5, 21)) + +const b1 = { value: su?.length }; +>b1 : Symbol(b1, Decl(optionalChainingInference.ts, 7, 5)) +>value : Symbol(value, Decl(optionalChainingInference.ts, 7, 12)) +>su?.length : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) +>su : Symbol(su, Decl(optionalChainingInference.ts, 2, 13)) +>length : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) + +const v1: number = unbox(b1); +>v1 : Symbol(v1, Decl(optionalChainingInference.ts, 8, 5)) +>unbox : Symbol(unbox, Decl(optionalChainingInference.ts, 0, 0)) +>b1 : Symbol(b1, Decl(optionalChainingInference.ts, 7, 5)) + +const b2 = { value: su?.length as number | undefined }; +>b2 : Symbol(b2, Decl(optionalChainingInference.ts, 10, 5)) +>value : Symbol(value, Decl(optionalChainingInference.ts, 10, 12)) +>su?.length : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) +>su : Symbol(su, Decl(optionalChainingInference.ts, 2, 13)) +>length : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) + +const v2: number = unbox(b2); +>v2 : Symbol(v2, Decl(optionalChainingInference.ts, 11, 5)) +>unbox : Symbol(unbox, Decl(optionalChainingInference.ts, 0, 0)) +>b2 : Symbol(b2, Decl(optionalChainingInference.ts, 10, 5)) + +const b3: { value: number | undefined } = { value: su?.length }; +>b3 : Symbol(b3, Decl(optionalChainingInference.ts, 13, 5)) +>value : Symbol(value, Decl(optionalChainingInference.ts, 13, 11)) +>value : Symbol(value, Decl(optionalChainingInference.ts, 13, 43)) +>su?.length : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) +>su : Symbol(su, Decl(optionalChainingInference.ts, 2, 13)) +>length : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) + +const v3: number = unbox(b3); +>v3 : Symbol(v3, Decl(optionalChainingInference.ts, 14, 5)) +>unbox : Symbol(unbox, Decl(optionalChainingInference.ts, 0, 0)) +>b3 : Symbol(b3, Decl(optionalChainingInference.ts, 13, 5)) + +const b4 = { value: fnu?.() }; +>b4 : Symbol(b4, Decl(optionalChainingInference.ts, 16, 5)) +>value : Symbol(value, Decl(optionalChainingInference.ts, 16, 12)) +>fnu : Symbol(fnu, Decl(optionalChainingInference.ts, 3, 13)) + +const v4: number = unbox(b4); +>v4 : Symbol(v4, Decl(optionalChainingInference.ts, 17, 5)) +>unbox : Symbol(unbox, Decl(optionalChainingInference.ts, 0, 0)) +>b4 : Symbol(b4, Decl(optionalChainingInference.ts, 16, 5)) + +const b5 = { value: su?.["length"] }; +>b5 : Symbol(b5, Decl(optionalChainingInference.ts, 19, 5)) +>value : Symbol(value, Decl(optionalChainingInference.ts, 19, 12)) +>su : Symbol(su, Decl(optionalChainingInference.ts, 2, 13)) +>"length" : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) + +const v5: number = unbox(b5); +>v5 : Symbol(v5, Decl(optionalChainingInference.ts, 20, 5)) +>unbox : Symbol(unbox, Decl(optionalChainingInference.ts, 0, 0)) +>b5 : Symbol(b5, Decl(optionalChainingInference.ts, 19, 5)) + +const b6 = { value: osu?.prop.length }; +>b6 : Symbol(b6, Decl(optionalChainingInference.ts, 22, 5)) +>value : Symbol(value, Decl(optionalChainingInference.ts, 22, 12)) +>osu?.prop.length : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) +>osu?.prop : Symbol(prop, Decl(optionalChainingInference.ts, 4, 20)) +>osu : Symbol(osu, Decl(optionalChainingInference.ts, 4, 13)) +>prop : Symbol(prop, Decl(optionalChainingInference.ts, 4, 20)) +>length : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) + +const v6: number = unbox(b6); +>v6 : Symbol(v6, Decl(optionalChainingInference.ts, 23, 5)) +>unbox : Symbol(unbox, Decl(optionalChainingInference.ts, 0, 0)) +>b6 : Symbol(b6, Decl(optionalChainingInference.ts, 22, 5)) + +const b7 = { value: osu?.prop["length"] }; +>b7 : Symbol(b7, Decl(optionalChainingInference.ts, 25, 5)) +>value : Symbol(value, Decl(optionalChainingInference.ts, 25, 12)) +>osu?.prop : Symbol(prop, Decl(optionalChainingInference.ts, 4, 20)) +>osu : Symbol(osu, Decl(optionalChainingInference.ts, 4, 13)) +>prop : Symbol(prop, Decl(optionalChainingInference.ts, 4, 20)) +>"length" : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) + +const v7: number = unbox(b7); +>v7 : Symbol(v7, Decl(optionalChainingInference.ts, 26, 5)) +>unbox : Symbol(unbox, Decl(optionalChainingInference.ts, 0, 0)) +>b7 : Symbol(b7, Decl(optionalChainingInference.ts, 25, 5)) + +const b8 = { value: ofnu?.prop() }; +>b8 : Symbol(b8, Decl(optionalChainingInference.ts, 28, 5)) +>value : Symbol(value, Decl(optionalChainingInference.ts, 28, 12)) +>ofnu?.prop : Symbol(prop, Decl(optionalChainingInference.ts, 5, 21)) +>ofnu : Symbol(ofnu, Decl(optionalChainingInference.ts, 5, 13)) +>prop : Symbol(prop, Decl(optionalChainingInference.ts, 5, 21)) + +const v8: number = unbox(b8); +>v8 : Symbol(v8, Decl(optionalChainingInference.ts, 29, 5)) +>unbox : Symbol(unbox, Decl(optionalChainingInference.ts, 0, 0)) +>b8 : Symbol(b8, Decl(optionalChainingInference.ts, 28, 5)) + + diff --git a/tests/baselines/reference/optionalChainingInference.types b/tests/baselines/reference/optionalChainingInference.types new file mode 100644 index 0000000000000..c2daa07306161 --- /dev/null +++ b/tests/baselines/reference/optionalChainingInference.types @@ -0,0 +1,140 @@ +=== tests/cases/conformance/expressions/optionalChaining/optionalChainingInference.ts === +// https://github.com/microsoft/TypeScript/issues/34579 +declare function unbox(box: { value: T | undefined }): T; +>unbox : (box: { value: T; }) => T +>box : { value: T; } +>value : T + +declare const su: string | undefined; +>su : string + +declare const fnu: (() => number) | undefined; +>fnu : () => number + +declare const osu: { prop: string } | undefined; +>osu : { prop: string; } +>prop : string + +declare const ofnu: { prop: () => number } | undefined; +>ofnu : { prop: () => number; } +>prop : () => number + +const b1 = { value: su?.length }; +>b1 : { value: number; } +>{ value: su?.length } : { value: number; } +>value : number +>su?.length : number +>su : string +>length : number + +const v1: number = unbox(b1); +>v1 : number +>unbox(b1) : number +>unbox : (box: { value: T; }) => T +>b1 : { value: number; } + +const b2 = { value: su?.length as number | undefined }; +>b2 : { value: number; } +>{ value: su?.length as number | undefined } : { value: number; } +>value : number +>su?.length as number | undefined : number +>su?.length : number +>su : string +>length : number + +const v2: number = unbox(b2); +>v2 : number +>unbox(b2) : number +>unbox : (box: { value: T; }) => T +>b2 : { value: number; } + +const b3: { value: number | undefined } = { value: su?.length }; +>b3 : { value: number; } +>value : number +>{ value: su?.length } : { value: number; } +>value : number +>su?.length : number +>su : string +>length : number + +const v3: number = unbox(b3); +>v3 : number +>unbox(b3) : number +>unbox : (box: { value: T; }) => T +>b3 : { value: number; } + +const b4 = { value: fnu?.() }; +>b4 : { value: number; } +>{ value: fnu?.() } : { value: number; } +>value : number +>fnu?.() : number +>fnu : () => number + +const v4: number = unbox(b4); +>v4 : number +>unbox(b4) : number +>unbox : (box: { value: T; }) => T +>b4 : { value: number; } + +const b5 = { value: su?.["length"] }; +>b5 : { value: number; } +>{ value: su?.["length"] } : { value: number; } +>value : number +>su?.["length"] : number +>su : string +>"length" : "length" + +const v5: number = unbox(b5); +>v5 : number +>unbox(b5) : number +>unbox : (box: { value: T; }) => T +>b5 : { value: number; } + +const b6 = { value: osu?.prop.length }; +>b6 : { value: number; } +>{ value: osu?.prop.length } : { value: number; } +>value : number +>osu?.prop.length : number +>osu?.prop : string +>osu : { prop: string; } +>prop : string +>length : number + +const v6: number = unbox(b6); +>v6 : number +>unbox(b6) : number +>unbox : (box: { value: T; }) => T +>b6 : { value: number; } + +const b7 = { value: osu?.prop["length"] }; +>b7 : { value: number; } +>{ value: osu?.prop["length"] } : { value: number; } +>value : number +>osu?.prop["length"] : number +>osu?.prop : string +>osu : { prop: string; } +>prop : string +>"length" : "length" + +const v7: number = unbox(b7); +>v7 : number +>unbox(b7) : number +>unbox : (box: { value: T; }) => T +>b7 : { value: number; } + +const b8 = { value: ofnu?.prop() }; +>b8 : { value: number; } +>{ value: ofnu?.prop() } : { value: number; } +>value : number +>ofnu?.prop() : number +>ofnu?.prop : () => number +>ofnu : { prop: () => number; } +>prop : () => number + +const v8: number = unbox(b8); +>v8 : number +>unbox(b8) : number +>unbox : (box: { value: T; }) => T +>b8 : { value: number; } + + diff --git a/tests/cases/conformance/expressions/optionalChaining/optionalChainingInference.ts b/tests/cases/conformance/expressions/optionalChaining/optionalChainingInference.ts new file mode 100644 index 0000000000000..1b321913a1682 --- /dev/null +++ b/tests/cases/conformance/expressions/optionalChaining/optionalChainingInference.ts @@ -0,0 +1,31 @@ +// https://github.com/microsoft/TypeScript/issues/34579 +declare function unbox(box: { value: T | undefined }): T; +declare const su: string | undefined; +declare const fnu: (() => number) | undefined; +declare const osu: { prop: string } | undefined; +declare const ofnu: { prop: () => number } | undefined; + +const b1 = { value: su?.length }; +const v1: number = unbox(b1); + +const b2 = { value: su?.length as number | undefined }; +const v2: number = unbox(b2); + +const b3: { value: number | undefined } = { value: su?.length }; +const v3: number = unbox(b3); + +const b4 = { value: fnu?.() }; +const v4: number = unbox(b4); + +const b5 = { value: su?.["length"] }; +const v5: number = unbox(b5); + +const b6 = { value: osu?.prop.length }; +const v6: number = unbox(b6); + +const b7 = { value: osu?.prop["length"] }; +const v7: number = unbox(b7); + +const b8 = { value: ofnu?.prop() }; +const v8: number = unbox(b8); +