Propagate 'undefined' instead of the optional type marker at an optional chain boundary (#34588)

* Propagate 'undefined' instead of the optional type marker at an optional chain boundary

* Update src/compiler/types.ts

Co-Authored-By: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com>
This commit is contained in:
Ron Buckton 2019-11-01 11:36:22 -07:00 committed by GitHub
parent ec367feb58
commit ba5e86f140
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 435 additions and 59 deletions

View File

@ -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)) {

View File

@ -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((<FunctionLikeDeclaration>signature.declaration).body) ? anyType : getReturnTypeFromBody(<FunctionLikeDeclaration>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);
}
}

View File

@ -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 */

View File

@ -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 && (<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

View File

@ -45,9 +45,9 @@ const n4: number | undefined = a?.m?.({x: absorb()}); // likewise
>a?.m : (<T>(obj: { x: T; }) => T) | undefined
>a : { m?<T>(obj: { x: T; }): T; } | undefined
>m : (<T>(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>() => T
// Also a test showing `!` vs `?` for good measure

View File

@ -0,0 +1,52 @@
//// [optionalChainingInference.ts]
// https://github.com/microsoft/TypeScript/issues/34579
declare function unbox<T>(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);

View File

@ -0,0 +1,122 @@
=== tests/cases/conformance/expressions/optionalChaining/optionalChainingInference.ts ===
// https://github.com/microsoft/TypeScript/issues/34579
declare function unbox<T>(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))

View File

@ -0,0 +1,140 @@
=== tests/cases/conformance/expressions/optionalChaining/optionalChainingInference.ts ===
// https://github.com/microsoft/TypeScript/issues/34579
declare function unbox<T>(box: { value: T | undefined }): T;
>unbox : <T>(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 : <T>(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 : <T>(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 : <T>(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 : <T>(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 : <T>(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 : <T>(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 : <T>(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 : <T>(box: { value: T; }) => T
>b8 : { value: number; }

View File

@ -0,0 +1,31 @@
// https://github.com/microsoft/TypeScript/issues/34579
declare function unbox<T>(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);