Fix destructuring and narrowing interaction (#47337)

* WIP: pass in checkmode to getNarrowableTypeForReference

* add tests

* another pass through new check mode argument

* rename new check mode

* only use rest flag for rest elements in objects

* add and update tests

* change check mode flag name

* restore package-lock.json

* fix comments

* get rid of fourslash tests

* fix caching in checkExpressionCached when checkMode is not normal

* Don't distinguish between object and array rest elements

* get rid of undefined check mode

* don't make includeOptionality into checkmode flag
This commit is contained in:
Gabriela Araujo Britto
2022-02-09 12:25:07 -08:00
committed by GitHub
parent d5c3015516
commit 2d0a00d457
7 changed files with 480 additions and 32 deletions
+52 -30
View File
@@ -175,12 +175,15 @@ namespace ts {
}
const enum CheckMode {
Normal = 0, // Normal type checking
Contextual = 1 << 0, // Explicitly assigned contextual type, therefore not cacheable
Inferential = 1 << 1, // Inferential typing
SkipContextSensitive = 1 << 2, // Skip context sensitive function expressions
SkipGenericFunctions = 1 << 3, // Skip single signature generic functions
IsForSignatureHelp = 1 << 4, // Call resolution for purposes of signature help
Normal = 0, // Normal type checking
Contextual = 1 << 0, // Explicitly assigned contextual type, therefore not cacheable
Inferential = 1 << 1, // Inferential typing
SkipContextSensitive = 1 << 2, // Skip context sensitive function expressions
SkipGenericFunctions = 1 << 3, // Skip single signature generic functions
IsForSignatureHelp = 1 << 4, // Call resolution for purposes of signature help
RestBindingElement = 1 << 5, // Checking a type that is going to be used to determine the type of a rest binding element
// e.g. in `const { a, ...rest } = foo`, when checking the type of `foo` to determine the type of `rest`,
// we need to preserve generic types instead of substituting them for constraints
}
const enum SignatureCheckMode {
@@ -8461,9 +8464,12 @@ namespace ts {
// Return the type of a binding element parent. We check SymbolLinks first to see if a type has been
// assigned by contextual typing.
function getTypeForBindingElementParent(node: BindingElementGrandparent) {
function getTypeForBindingElementParent(node: BindingElementGrandparent, checkMode: CheckMode) {
if (checkMode !== CheckMode.Normal) {
return getTypeForVariableLikeDeclaration(node, /*includeOptionality*/ false, checkMode);
}
const symbol = getSymbolOfNode(node);
return symbol && getSymbolLinks(symbol).type || getTypeForVariableLikeDeclaration(node, /*includeOptionality*/ false);
return symbol && getSymbolLinks(symbol).type || getTypeForVariableLikeDeclaration(node, /*includeOptionality*/ false, checkMode);
}
function getRestType(source: Type, properties: PropertyName[], symbol: Symbol | undefined): Type {
@@ -8594,7 +8600,8 @@ namespace ts {
/** Return the inferred type for a binding element */
function getTypeForBindingElement(declaration: BindingElement): Type | undefined {
const parentType = getTypeForBindingElementParent(declaration.parent.parent);
const checkMode = declaration.dotDotDotToken ? CheckMode.RestBindingElement : CheckMode.Normal;
const parentType = getTypeForBindingElementParent(declaration.parent.parent, checkMode);
return parentType && getBindingElementTypeFromParentType(declaration, parentType);
}
@@ -8667,9 +8674,9 @@ namespace ts {
if (getEffectiveTypeAnnotationNode(walkUpBindingElementsAndPatterns(declaration))) {
// In strict null checking mode, if a default value of a non-undefined type is specified, remove
// undefined from the final type.
return strictNullChecks && !(getFalsyFlags(checkDeclarationInitializer(declaration)) & TypeFlags.Undefined) ? getNonUndefinedType(type) : type;
return strictNullChecks && !(getFalsyFlags(checkDeclarationInitializer(declaration, CheckMode.Normal)) & TypeFlags.Undefined) ? getNonUndefinedType(type) : type;
}
return widenTypeInferredFromInitializer(declaration, getUnionType([getNonUndefinedType(type), checkDeclarationInitializer(declaration)], UnionReduction.Subtype));
return widenTypeInferredFromInitializer(declaration, getUnionType([getNonUndefinedType(type), checkDeclarationInitializer(declaration, CheckMode.Normal)], UnionReduction.Subtype));
}
function getTypeForDeclarationFromJSDocComment(declaration: Node) {
@@ -8695,11 +8702,15 @@ namespace ts {
}
// Return the inferred type for a variable, parameter, or property declaration
function getTypeForVariableLikeDeclaration(declaration: ParameterDeclaration | PropertyDeclaration | PropertySignature | VariableDeclaration | BindingElement | JSDocPropertyLikeTag, includeOptionality: boolean): Type | undefined {
function getTypeForVariableLikeDeclaration(
declaration: ParameterDeclaration | PropertyDeclaration | PropertySignature | VariableDeclaration | BindingElement | JSDocPropertyLikeTag,
includeOptionality: boolean,
checkMode: CheckMode,
): Type | undefined {
// A variable declared in a for..in statement is of type string, or of type keyof T when the
// right hand expression is of a type parameter type.
if (isVariableDeclaration(declaration) && declaration.parent.parent.kind === SyntaxKind.ForInStatement) {
const indexType = getIndexType(getNonNullableTypeIfNeeded(checkExpression(declaration.parent.parent.expression)));
const indexType = getIndexType(getNonNullableTypeIfNeeded(checkExpression(declaration.parent.parent.expression, /*checkMode*/ checkMode)));
return indexType.flags & (TypeFlags.TypeParameter | TypeFlags.Index) ? getExtractStringType(indexType) : stringType;
}
@@ -8784,7 +8795,7 @@ namespace ts {
return containerObjectType;
}
}
const type = widenTypeInferredFromInitializer(declaration, checkDeclarationInitializer(declaration));
const type = widenTypeInferredFromInitializer(declaration, checkDeclarationInitializer(declaration, checkMode));
return addOptionality(type, isProperty, isOptional);
}
@@ -9177,7 +9188,7 @@ namespace ts {
// contextual type or, if the element itself is a binding pattern, with the type implied by that binding
// pattern.
const contextualType = isBindingPattern(element.name) ? getTypeFromBindingPattern(element.name, /*includePatternInType*/ true, /*reportErrors*/ false) : unknownType;
return addOptionality(widenTypeInferredFromInitializer(element, checkDeclarationInitializer(element, contextualType)));
return addOptionality(widenTypeInferredFromInitializer(element, checkDeclarationInitializer(element, CheckMode.Normal, contextualType)));
}
if (isBindingPattern(element.name)) {
return getTypeFromBindingPattern(element.name, includePatternInType, reportErrors);
@@ -9269,7 +9280,7 @@ namespace ts {
// binding pattern [x, s = ""]. Because the contextual type is a tuple type, the resulting type of [1, "one"] is the
// tuple type [number, string]. Thus, the type inferred for 'x' is number and the type inferred for 's' is string.
function getWidenedTypeForVariableLikeDeclaration(declaration: ParameterDeclaration | PropertyDeclaration | PropertySignature | VariableDeclaration | BindingElement | JSDocPropertyLikeTag, reportErrors?: boolean): Type {
return widenTypeForVariableLikeDeclaration(getTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ true), declaration, reportErrors);
return widenTypeForVariableLikeDeclaration(getTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ true, CheckMode.Normal), declaration, reportErrors);
}
function isGlobalSymbolConstructor(node: Node) {
@@ -25019,12 +25030,16 @@ namespace ts {
return !!(type.flags & TypeFlags.Instantiable && !maybeTypeOfKind(getBaseConstraintOrType(type), TypeFlags.Nullable));
}
function hasNonBindingPatternContextualTypeWithNoGenericTypes(node: Node) {
function hasContextualTypeWithNoGenericTypes(node: Node, checkMode: CheckMode | undefined) {
// Computing the contextual type for a child of a JSX element involves resolving the type of the
// element's tag name, so we exclude that here to avoid circularities.
// If check mode has `CheckMode.RestBindingElement`, we skip binding pattern contextual types,
// as we want the type of a rest element to be generic when possible.
const contextualType = (isIdentifier(node) || isPropertyAccessExpression(node) || isElementAccessExpression(node)) &&
!((isJsxOpeningElement(node.parent) || isJsxSelfClosingElement(node.parent)) && node.parent.tagName === node) &&
getContextualType(node, ContextFlags.SkipBindingPatterns);
(checkMode && checkMode & CheckMode.RestBindingElement ?
getContextualType(node, ContextFlags.SkipBindingPatterns)
: getContextualType(node));
return contextualType && !isGenericType(contextualType);
}
@@ -25038,7 +25053,7 @@ namespace ts {
// 'string | undefined' to give control flow analysis the opportunity to narrow to type 'string'.
const substituteConstraints = !(checkMode && checkMode & CheckMode.Inferential) &&
someType(type, isGenericTypeWithUnionConstraint) &&
(isConstraintPosition(type, reference) || hasNonBindingPatternContextualTypeWithNoGenericTypes(reference));
(isConstraintPosition(type, reference) || hasContextualTypeWithNoGenericTypes(reference, checkMode));
return substituteConstraints ? mapType(type, t => t.flags & TypeFlags.Instantiable && !isMappedTypeGenericIndexedAccess(t) ? getBaseConstraintOrType(t) : t) : type;
}
@@ -25110,7 +25125,7 @@ namespace ts {
const links = getNodeLinks(location);
if (!(links.flags & NodeCheckFlags.InCheckIdentifier)) {
links.flags |= NodeCheckFlags.InCheckIdentifier;
const parentType = getTypeForBindingElementParent(parent);
const parentType = getTypeForBindingElementParent(parent, CheckMode.Normal);
links.flags &= ~NodeCheckFlags.InCheckIdentifier;
if (parentType && parentType.flags & TypeFlags.Union && !(parent.kind === SyntaxKind.Parameter && isSymbolAssigned(symbol))) {
const pattern = declaration.parent;
@@ -26047,7 +26062,7 @@ namespace ts {
const parent = declaration.parent.parent;
const name = declaration.propertyName || declaration.name;
const parentType = getContextualTypeForVariableLikeDeclaration(parent) ||
parent.kind !== SyntaxKind.BindingElement && parent.initializer && checkDeclarationInitializer(parent);
parent.kind !== SyntaxKind.BindingElement && parent.initializer && checkDeclarationInitializer(parent, declaration.dotDotDotToken ? CheckMode.RestBindingElement : CheckMode.Normal);
if (!parentType || isBindingPattern(name) || isComputedNonLiteralName(name)) return undefined;
if (parent.name.kind === SyntaxKind.ArrayBindingPattern) {
const index = indexOfNode(declaration.parent.elements, declaration);
@@ -31807,7 +31822,7 @@ namespace ts {
const links = getSymbolLinks(parameter);
if (!links.type) {
const declaration = parameter.valueDeclaration as ParameterDeclaration;
links.type = type || getWidenedTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ true);
links.type = type || getWidenedTypeForVariableLikeDeclaration(declaration, /*reportErrors*/ true);
if (declaration.name.kind !== SyntaxKind.Identifier) {
// if inference didn't come up with anything but unknown, fall back to the binding pattern if present.
if (links.type === unknownType) {
@@ -33724,11 +33739,11 @@ namespace ts {
}
function checkExpressionCached(node: Expression | QualifiedName, checkMode?: CheckMode): Type {
if (checkMode && checkMode !== CheckMode.Normal) {
return checkExpression(node, checkMode);
}
const links = getNodeLinks(node);
if (!links.resolvedType) {
if (checkMode && checkMode !== CheckMode.Normal) {
return checkExpression(node, checkMode);
}
// When computing a type that we're going to cache, we need to ignore any ongoing control flow
// analysis because variables may have transient types in indeterminable states. Moving flowLoopStart
// to the top of the stack ensures all transient types are computed from a known point.
@@ -33750,10 +33765,16 @@ namespace ts {
isJSDocTypeAssertion(node);
}
function checkDeclarationInitializer(declaration: HasExpressionInitializer, contextualType?: Type | undefined) {
function checkDeclarationInitializer(
declaration: HasExpressionInitializer,
checkMode: CheckMode,
contextualType?: Type | undefined
) {
const initializer = getEffectiveInitializer(declaration)!;
const type = getQuickTypeOfExpression(initializer) ||
(contextualType ? checkExpressionWithContextualType(initializer, contextualType, /*inferenceContext*/ undefined, CheckMode.Normal) : checkExpressionCached(initializer));
(contextualType ?
checkExpressionWithContextualType(initializer, contextualType, /*inferenceContext*/ undefined, checkMode || CheckMode.Normal)
: checkExpressionCached(initializer, checkMode));
return isParameter(declaration) && declaration.name.kind === SyntaxKind.ArrayBindingPattern &&
isTupleType(type) && !type.target.hasRestElement && getTypeReferenceArity(type) < declaration.name.elements.length ?
padTupleType(type, declaration.name) : type;
@@ -37011,7 +37032,8 @@ namespace ts {
// check private/protected variable access
const parent = node.parent.parent;
const parentType = getTypeForBindingElementParent(parent);
const parentCheckMode = node.dotDotDotToken ? CheckMode.RestBindingElement : CheckMode.Normal;
const parentType = getTypeForBindingElementParent(parent, parentCheckMode);
const name = node.propertyName || node.name;
if (parentType && !isBindingPattern(name)) {
const exprType = getLiteralTypeFromPropertyName(name);
@@ -38409,7 +38431,7 @@ namespace ts {
const declaration = catchClause.variableDeclaration;
const typeNode = getEffectiveTypeAnnotationNode(getRootDeclaration(declaration));
if (typeNode) {
const type = getTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ false);
const type = getTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ false, CheckMode.Normal);
if (type && !(type.flags & TypeFlags.AnyOrUnknown)) {
grammarErrorOnFirstToken(typeNode, Diagnostics.Catch_clause_variable_type_annotation_must_be_any_or_unknown_if_specified);
}
@@ -41440,7 +41462,7 @@ namespace ts {
}
if (isBindingPattern(node)) {
return getTypeForVariableLikeDeclaration(node.parent, /*includeOptionality*/ true) || errorType;
return getTypeForVariableLikeDeclaration(node.parent, /*includeOptionality*/ true, CheckMode.Normal) || errorType;
}
if (isInRightSideOfImportOrExportAssignment(node as Identifier)) {
@@ -25,7 +25,7 @@ const getType = <P extends Params>(params: P) => {
>rest : Omit<P, "foo">
} = params;
>params : P
>params : Params
return rest;
>rest : Omit<P, "foo">
@@ -0,0 +1,89 @@
//// [narrowingDestructuring.ts]
type X = { kind: "a", a: string } | { kind: "b", b: string }
function func<T extends X>(value: T) {
if (value.kind === "a") {
value.a;
const { a } = value;
} else {
value.b;
const { b } = value;
}
}
type Z = { kind: "f", f: { a: number, b: string, c: number } }
| { kind: "g", g: { a: string, b: number, c: string }};
function func2<T extends Z>(value: T) {
if (value.kind === "f") {
const { f: f1 } = value;
const { f: { a, ...spread } } = value;
value.f;
} else {
const { g: { c, ...spread } } = value;
value.g;
}
}
function func3<T extends { kind: "a", a: string } | { kind: "b", b: number }>(t: T) {
if (t.kind === "a") {
const { kind, ...r1 } = t;
const r2 = (({ kind, ...rest }) => rest)(t);
}
}
function farr<T extends [number, string, string] | [string, number, number]>(x: T) {
const [head, ...tail] = x;
if (x[0] === 'number') {
const [head, ...tail] = x;
}
}
//// [narrowingDestructuring.js]
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;
};
function func(value) {
if (value.kind === "a") {
value.a;
var a = value.a;
}
else {
value.b;
var b = value.b;
}
}
function func2(value) {
if (value.kind === "f") {
var f1 = value.f;
var _a = value.f, a = _a.a, spread = __rest(_a, ["a"]);
value.f;
}
else {
var _b = value.g, c = _b.c, spread = __rest(_b, ["c"]);
value.g;
}
}
function func3(t) {
if (t.kind === "a") {
var kind = t.kind, r1 = __rest(t, ["kind"]);
var r2 = (function (_a) {
var kind = _a.kind, rest = __rest(_a, ["kind"]);
return rest;
})(t);
}
}
function farr(x) {
var head = x[0], tail = x.slice(1);
if (x[0] === 'number') {
var head_1 = x[0], tail_1 = x.slice(1);
}
}
@@ -0,0 +1,148 @@
=== tests/cases/compiler/narrowingDestructuring.ts ===
type X = { kind: "a", a: string } | { kind: "b", b: string }
>X : Symbol(X, Decl(narrowingDestructuring.ts, 0, 0))
>kind : Symbol(kind, Decl(narrowingDestructuring.ts, 0, 10))
>a : Symbol(a, Decl(narrowingDestructuring.ts, 0, 21))
>kind : Symbol(kind, Decl(narrowingDestructuring.ts, 0, 37))
>b : Symbol(b, Decl(narrowingDestructuring.ts, 0, 48))
function func<T extends X>(value: T) {
>func : Symbol(func, Decl(narrowingDestructuring.ts, 0, 60))
>T : Symbol(T, Decl(narrowingDestructuring.ts, 2, 14))
>X : Symbol(X, Decl(narrowingDestructuring.ts, 0, 0))
>value : Symbol(value, Decl(narrowingDestructuring.ts, 2, 27))
>T : Symbol(T, Decl(narrowingDestructuring.ts, 2, 14))
if (value.kind === "a") {
>value.kind : Symbol(kind, Decl(narrowingDestructuring.ts, 0, 10), Decl(narrowingDestructuring.ts, 0, 37))
>value : Symbol(value, Decl(narrowingDestructuring.ts, 2, 27))
>kind : Symbol(kind, Decl(narrowingDestructuring.ts, 0, 10), Decl(narrowingDestructuring.ts, 0, 37))
value.a;
>value.a : Symbol(a, Decl(narrowingDestructuring.ts, 0, 21))
>value : Symbol(value, Decl(narrowingDestructuring.ts, 2, 27))
>a : Symbol(a, Decl(narrowingDestructuring.ts, 0, 21))
const { a } = value;
>a : Symbol(a, Decl(narrowingDestructuring.ts, 5, 15))
>value : Symbol(value, Decl(narrowingDestructuring.ts, 2, 27))
} else {
value.b;
>value.b : Symbol(b, Decl(narrowingDestructuring.ts, 0, 48))
>value : Symbol(value, Decl(narrowingDestructuring.ts, 2, 27))
>b : Symbol(b, Decl(narrowingDestructuring.ts, 0, 48))
const { b } = value;
>b : Symbol(b, Decl(narrowingDestructuring.ts, 8, 15))
>value : Symbol(value, Decl(narrowingDestructuring.ts, 2, 27))
}
}
type Z = { kind: "f", f: { a: number, b: string, c: number } }
>Z : Symbol(Z, Decl(narrowingDestructuring.ts, 10, 1))
>kind : Symbol(kind, Decl(narrowingDestructuring.ts, 12, 10))
>f : Symbol(f, Decl(narrowingDestructuring.ts, 12, 21))
>a : Symbol(a, Decl(narrowingDestructuring.ts, 12, 26))
>b : Symbol(b, Decl(narrowingDestructuring.ts, 12, 37))
>c : Symbol(c, Decl(narrowingDestructuring.ts, 12, 48))
| { kind: "g", g: { a: string, b: number, c: string }};
>kind : Symbol(kind, Decl(narrowingDestructuring.ts, 13, 7))
>g : Symbol(g, Decl(narrowingDestructuring.ts, 13, 18))
>a : Symbol(a, Decl(narrowingDestructuring.ts, 13, 23))
>b : Symbol(b, Decl(narrowingDestructuring.ts, 13, 34))
>c : Symbol(c, Decl(narrowingDestructuring.ts, 13, 45))
function func2<T extends Z>(value: T) {
>func2 : Symbol(func2, Decl(narrowingDestructuring.ts, 13, 59))
>T : Symbol(T, Decl(narrowingDestructuring.ts, 15, 15))
>Z : Symbol(Z, Decl(narrowingDestructuring.ts, 10, 1))
>value : Symbol(value, Decl(narrowingDestructuring.ts, 15, 28))
>T : Symbol(T, Decl(narrowingDestructuring.ts, 15, 15))
if (value.kind === "f") {
>value.kind : Symbol(kind, Decl(narrowingDestructuring.ts, 12, 10), Decl(narrowingDestructuring.ts, 13, 7))
>value : Symbol(value, Decl(narrowingDestructuring.ts, 15, 28))
>kind : Symbol(kind, Decl(narrowingDestructuring.ts, 12, 10), Decl(narrowingDestructuring.ts, 13, 7))
const { f: f1 } = value;
>f : Symbol(f, Decl(narrowingDestructuring.ts, 12, 21))
>f1 : Symbol(f1, Decl(narrowingDestructuring.ts, 17, 15))
>value : Symbol(value, Decl(narrowingDestructuring.ts, 15, 28))
const { f: { a, ...spread } } = value;
>f : Symbol(f, Decl(narrowingDestructuring.ts, 12, 21))
>a : Symbol(a, Decl(narrowingDestructuring.ts, 18, 20))
>spread : Symbol(spread, Decl(narrowingDestructuring.ts, 18, 23))
>value : Symbol(value, Decl(narrowingDestructuring.ts, 15, 28))
value.f;
>value.f : Symbol(f, Decl(narrowingDestructuring.ts, 12, 21))
>value : Symbol(value, Decl(narrowingDestructuring.ts, 15, 28))
>f : Symbol(f, Decl(narrowingDestructuring.ts, 12, 21))
} else {
const { g: { c, ...spread } } = value;
>g : Symbol(g, Decl(narrowingDestructuring.ts, 13, 18))
>c : Symbol(c, Decl(narrowingDestructuring.ts, 21, 20))
>spread : Symbol(spread, Decl(narrowingDestructuring.ts, 21, 23))
>value : Symbol(value, Decl(narrowingDestructuring.ts, 15, 28))
value.g;
>value.g : Symbol(g, Decl(narrowingDestructuring.ts, 13, 18))
>value : Symbol(value, Decl(narrowingDestructuring.ts, 15, 28))
>g : Symbol(g, Decl(narrowingDestructuring.ts, 13, 18))
}
}
function func3<T extends { kind: "a", a: string } | { kind: "b", b: number }>(t: T) {
>func3 : Symbol(func3, Decl(narrowingDestructuring.ts, 24, 1))
>T : Symbol(T, Decl(narrowingDestructuring.ts, 26, 15))
>kind : Symbol(kind, Decl(narrowingDestructuring.ts, 26, 26))
>a : Symbol(a, Decl(narrowingDestructuring.ts, 26, 37))
>kind : Symbol(kind, Decl(narrowingDestructuring.ts, 26, 53))
>b : Symbol(b, Decl(narrowingDestructuring.ts, 26, 64))
>t : Symbol(t, Decl(narrowingDestructuring.ts, 26, 78))
>T : Symbol(T, Decl(narrowingDestructuring.ts, 26, 15))
if (t.kind === "a") {
>t.kind : Symbol(kind, Decl(narrowingDestructuring.ts, 26, 26), Decl(narrowingDestructuring.ts, 26, 53))
>t : Symbol(t, Decl(narrowingDestructuring.ts, 26, 78))
>kind : Symbol(kind, Decl(narrowingDestructuring.ts, 26, 26), Decl(narrowingDestructuring.ts, 26, 53))
const { kind, ...r1 } = t;
>kind : Symbol(kind, Decl(narrowingDestructuring.ts, 28, 15))
>r1 : Symbol(r1, Decl(narrowingDestructuring.ts, 28, 21))
>t : Symbol(t, Decl(narrowingDestructuring.ts, 26, 78))
const r2 = (({ kind, ...rest }) => rest)(t);
>r2 : Symbol(r2, Decl(narrowingDestructuring.ts, 29, 13))
>kind : Symbol(kind, Decl(narrowingDestructuring.ts, 29, 22))
>rest : Symbol(rest, Decl(narrowingDestructuring.ts, 29, 28))
>rest : Symbol(rest, Decl(narrowingDestructuring.ts, 29, 28))
>t : Symbol(t, Decl(narrowingDestructuring.ts, 26, 78))
}
}
function farr<T extends [number, string, string] | [string, number, number]>(x: T) {
>farr : Symbol(farr, Decl(narrowingDestructuring.ts, 31, 1))
>T : Symbol(T, Decl(narrowingDestructuring.ts, 33, 14))
>x : Symbol(x, Decl(narrowingDestructuring.ts, 33, 77))
>T : Symbol(T, Decl(narrowingDestructuring.ts, 33, 14))
const [head, ...tail] = x;
>head : Symbol(head, Decl(narrowingDestructuring.ts, 34, 11))
>tail : Symbol(tail, Decl(narrowingDestructuring.ts, 34, 16))
>x : Symbol(x, Decl(narrowingDestructuring.ts, 33, 77))
if (x[0] === 'number') {
>x : Symbol(x, Decl(narrowingDestructuring.ts, 33, 77))
>0 : Symbol(0)
const [head, ...tail] = x;
>head : Symbol(head, Decl(narrowingDestructuring.ts, 36, 15))
>tail : Symbol(tail, Decl(narrowingDestructuring.ts, 36, 20))
>x : Symbol(x, Decl(narrowingDestructuring.ts, 33, 77))
}
}
@@ -0,0 +1,150 @@
=== tests/cases/compiler/narrowingDestructuring.ts ===
type X = { kind: "a", a: string } | { kind: "b", b: string }
>X : X
>kind : "a"
>a : string
>kind : "b"
>b : string
function func<T extends X>(value: T) {
>func : <T extends X>(value: T) => void
>value : T
if (value.kind === "a") {
>value.kind === "a" : boolean
>value.kind : "a" | "b"
>value : X
>kind : "a" | "b"
>"a" : "a"
value.a;
>value.a : string
>value : { kind: "a"; a: string; }
>a : string
const { a } = value;
>a : string
>value : { kind: "a"; a: string; }
} else {
value.b;
>value.b : string
>value : { kind: "b"; b: string; }
>b : string
const { b } = value;
>b : string
>value : { kind: "b"; b: string; }
}
}
type Z = { kind: "f", f: { a: number, b: string, c: number } }
>Z : Z
>kind : "f"
>f : { a: number; b: string; c: number; }
>a : number
>b : string
>c : number
| { kind: "g", g: { a: string, b: number, c: string }};
>kind : "g"
>g : { a: string; b: number; c: string; }
>a : string
>b : number
>c : string
function func2<T extends Z>(value: T) {
>func2 : <T extends Z>(value: T) => void
>value : T
if (value.kind === "f") {
>value.kind === "f" : boolean
>value.kind : "f" | "g"
>value : Z
>kind : "f" | "g"
>"f" : "f"
const { f: f1 } = value;
>f : any
>f1 : { a: number; b: string; c: number; }
>value : { kind: "f"; f: { a: number; b: string; c: number; }; }
const { f: { a, ...spread } } = value;
>f : any
>a : number
>spread : { b: string; c: number; }
>value : { kind: "f"; f: { a: number; b: string; c: number; }; }
value.f;
>value.f : { a: number; b: string; c: number; }
>value : { kind: "f"; f: { a: number; b: string; c: number; }; }
>f : { a: number; b: string; c: number; }
} else {
const { g: { c, ...spread } } = value;
>g : any
>c : string
>spread : { a: string; b: number; }
>value : { kind: "g"; g: { a: string; b: number; c: string; }; }
value.g;
>value.g : { a: string; b: number; c: string; }
>value : { kind: "g"; g: { a: string; b: number; c: string; }; }
>g : { a: string; b: number; c: string; }
}
}
function func3<T extends { kind: "a", a: string } | { kind: "b", b: number }>(t: T) {
>func3 : <T extends { kind: "a"; a: string; } | { kind: "b"; b: number; }>(t: T) => void
>kind : "a"
>a : string
>kind : "b"
>b : number
>t : T
if (t.kind === "a") {
>t.kind === "a" : boolean
>t.kind : "a" | "b"
>t : { kind: "a"; a: string; } | { kind: "b"; b: number; }
>kind : "a" | "b"
>"a" : "a"
const { kind, ...r1 } = t;
>kind : "a"
>r1 : Omit<T, "kind">
>t : { kind: "a"; a: string; }
const r2 = (({ kind, ...rest }) => rest)(t);
>r2 : { a: string; }
>(({ kind, ...rest }) => rest)(t) : { a: string; }
>(({ kind, ...rest }) => rest) : ({ kind, ...rest }: { kind: "a"; a: string; }) => { a: string; }
>({ kind, ...rest }) => rest : ({ kind, ...rest }: { kind: "a"; a: string; }) => { a: string; }
>kind : "a"
>rest : { a: string; }
>rest : { a: string; }
>t : { kind: "a"; a: string; }
}
}
function farr<T extends [number, string, string] | [string, number, number]>(x: T) {
>farr : <T extends [number, string, string] | [string, number, number]>(x: T) => void
>x : T
const [head, ...tail] = x;
>head : string | number
>tail : (string | number)[]
>x : [number, string, string] | [string, number, number]
if (x[0] === 'number') {
>x[0] === 'number' : boolean
>x[0] : string | number
>x : [number, string, string] | [string, number, number]
>0 : 0
>'number' : "number"
const [head, ...tail] = x;
>head : "number"
>tail : (string | number)[]
>x : [number, string, string] | [string, number, number]
}
}
@@ -84,7 +84,7 @@ function f<T extends { b: string }>(p1: T, p2: T[]) {
var {...r5} = k; // Error, index
>r5 : any
>k : keyof T
>k : string | number | symbol
var {...r6} = mapped_generic; // Error, generic mapped object type
>r6 : { [P in keyof T]: T[P]; }
@@ -0,0 +1,39 @@
type X = { kind: "a", a: string } | { kind: "b", b: string }
function func<T extends X>(value: T) {
if (value.kind === "a") {
value.a;
const { a } = value;
} else {
value.b;
const { b } = value;
}
}
type Z = { kind: "f", f: { a: number, b: string, c: number } }
| { kind: "g", g: { a: string, b: number, c: string }};
function func2<T extends Z>(value: T) {
if (value.kind === "f") {
const { f: f1 } = value;
const { f: { a, ...spread } } = value;
value.f;
} else {
const { g: { c, ...spread } } = value;
value.g;
}
}
function func3<T extends { kind: "a", a: string } | { kind: "b", b: number }>(t: T) {
if (t.kind === "a") {
const { kind, ...r1 } = t;
const r2 = (({ kind, ...rest }) => rest)(t);
}
}
function farr<T extends [number, string, string] | [string, number, number]>(x: T) {
const [head, ...tail] = x;
if (x[0] === 'number') {
const [head, ...tail] = x;
}
}