Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 19 additions & 16 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ import {
BindableObjectDefinePropertyCall,
BindableStaticNameExpression,
BindingElement,
BindingElementGrandparent,
BindingName,
BindingPattern,
bindSourceFile,
Expand Down Expand Up @@ -11527,12 +11526,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {

// 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, checkMode: CheckMode) {
if (checkMode !== CheckMode.Normal) {
return getTypeForVariableLikeDeclaration(node, /*includeOptionality*/ false, checkMode);
function getTypeForBindingElementParent(node: BindingElement) {
const grandparent = node.parent.parent;
const rootParameter = tryGetRootParameterDeclaration(node);
if (rootParameter) {
const symbol = getSymbolOfDeclaration(grandparent);
const contextualParameterType = symbol && getSymbolLinks(symbol).type;
if (contextualParameterType) {
return contextualParameterType;
}
}
const symbol = getSymbolOfDeclaration(node);
return symbol && getSymbolLinks(symbol).type || getTypeForVariableLikeDeclaration(node, /*includeOptionality*/ false, checkMode);
return getTypeForVariableLikeDeclaration(grandparent, /*includeOptionality*/ false, node.dotDotDotToken ? CheckMode.RestBindingElement : CheckMode.Normal);
}

function getRestType(source: Type, properties: PropertyName[], symbol: Symbol | undefined): Type {
Expand Down Expand Up @@ -11665,8 +11669,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {

/** Return the inferred type for a binding element */
function getTypeForBindingElement(declaration: BindingElement): Type | undefined {
const checkMode = declaration.dotDotDotToken ? CheckMode.RestBindingElement : CheckMode.Normal;
const parentType = getTypeForBindingElementParent(declaration.parent.parent, checkMode);
const parentType = getTypeForBindingElementParent(declaration);
return parentType && getBindingElementTypeFromParentType(declaration, parentType, /*noTupleBoundsCheck*/ false);
}

Expand Down Expand Up @@ -30811,7 +30814,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const type = getTypeOfSymbol(symbol);
const declaration = symbol.valueDeclaration;
if (declaration) {
// If we have a non-rest binding element with no initializer declared as a const variable or a const-like
// If we have a binding element with no initializer declared as a const variable or a const-like
// parameter (a parameter for which there are no assignments in the function body), and if the parent type
// for the destructuring is a union type, one or more of the binding elements may represent discriminant
// properties, and we want the effects of conditional checks on such discriminants to affect the types of
Expand All @@ -30834,19 +30837,20 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// the binding pattern AST instance for '{ kind, payload }' as a pseudo-reference and narrow this reference
// as if it occurred in the specified location. We then recompute the narrowed binding element type by
// destructuring from the narrowed parent type.
if (isBindingElement(declaration) && !declaration.initializer && !declaration.dotDotDotToken && declaration.parent.elements.length >= 2) {
if (isBindingElement(declaration) && !declaration.initializer && declaration.parent.elements.length >= 2) {
const parent = declaration.parent.parent;
const rootDeclaration = getRootDeclaration(parent);
if (rootDeclaration.kind === SyntaxKind.VariableDeclaration && getCombinedNodeFlagsCached(rootDeclaration) & NodeFlags.Constant || rootDeclaration.kind === SyntaxKind.Parameter) {
const links = getNodeLinks(parent);
if (!(links.flags & NodeCheckFlags.InCheckIdentifier)) {
links.flags |= NodeCheckFlags.InCheckIdentifier;
const parentType = getTypeForBindingElementParent(parent, CheckMode.Normal);
const parentTypeConstraint = parentType && mapType(parentType, getBaseConstraintOrType);
const parentType = getTypeForBindingElementParent(declaration);
const parentNarrowableType = parentType && getNarrowableTypeForReference(parentType, location);

links.flags &= ~NodeCheckFlags.InCheckIdentifier;
if (parentTypeConstraint && parentTypeConstraint.flags & TypeFlags.Union && !(rootDeclaration.kind === SyntaxKind.Parameter && isSomeSymbolAssigned(rootDeclaration))) {
if (parentNarrowableType && parentNarrowableType.flags & TypeFlags.Union && !(rootDeclaration.kind === SyntaxKind.Parameter && isSomeSymbolAssigned(rootDeclaration))) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When obtaining the parent type we don't want to get its constraints substituted based on its contextual type or it being in a constraint position or something. This would lead to returning its constraint and then using that for getBindingElementTypeFromParentType even when the current location isn't at the constraint position.

const pattern = declaration.parent;
const narrowedType = getFlowTypeOfReference(pattern, parentTypeConstraint, parentTypeConstraint, /*flowContainer*/ undefined, location.flowNode);
const narrowedType = getFlowTypeOfReference(pattern, parentNarrowableType, parentNarrowableType, /*flowContainer*/ undefined, location.flowNode);
if (narrowedType.flags & TypeFlags.Never) {
return neverType;
}
Expand Down Expand Up @@ -44917,8 +44921,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {

// check private/protected variable access
const parent = node.parent.parent;
const parentCheckMode = node.dotDotDotToken ? CheckMode.RestBindingElement : CheckMode.Normal;
const parentType = getTypeForBindingElementParent(parent, parentCheckMode);
const parentType = getTypeForBindingElementParent(node);
const name = node.propertyName || node.name;
if (parentType && !isBindingPattern(name)) {
const exprType = getLiteralTypeFromPropertyName(name);
Expand Down
3 changes: 0 additions & 3 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1911,9 +1911,6 @@ export interface BindingElement extends NamedDeclaration, FlowContainer {
readonly initializer?: Expression; // Optional initializer
}

/** @internal */
export type BindingElementGrandparent = BindingElement["parent"]["parent"];

// dprint-ignore
export interface PropertySignature extends TypeElement, JSDocContainer {
readonly kind: SyntaxKind.PropertySignature;
Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/arrayDestructuringInSwitch1.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ export function evaluate(expression: Expression): boolean {

case 'and': {
return operands.every((child) => evaluate(child));
>operands.every : Symbol(Array.every, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>operands.every : Symbol(Array.every, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>operands : Symbol(operands, Decl(arrayDestructuringInSwitch1.ts, 5, 20))
>every : Symbol(Array.every, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>every : Symbol(Array.every, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>child : Symbol(child, Decl(arrayDestructuringInSwitch1.ts, 8, 31))
>evaluate : Symbol(evaluate, Decl(arrayDestructuringInSwitch1.ts, 1, 84))
>child : Symbol(child, Decl(arrayDestructuringInSwitch1.ts, 8, 31))
Expand Down
16 changes: 8 additions & 8 deletions tests/baselines/reference/arrayDestructuringInSwitch1.types
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,12 @@ export function evaluate(expression: Expression): boolean {
return operands.every((child) => evaluate(child));
>operands.every((child) => evaluate(child)) : boolean
> : ^^^^^^^
>operands.every : { <S extends Expression>(predicate: (value: Expression, index: number, array: Expression[]) => value is S, thisArg?: any): this is S[]; (predicate: (value: Expression, index: number, array: Expression[]) => unknown, thisArg?: any): boolean; } | { <S extends Expression>(predicate: (value: Expression, index: number, array: Expression[]) => value is S, thisArg?: any): this is S[]; (predicate: (value: Expression, index: number, array: Expression[]) => unknown, thisArg?: any): boolean; }
> : ^^^ ^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^ ^ ^^^ ^^^ ^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^ ^^ ^^^ ^^^ ^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^ ^ ^^^ ^^^ ^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^ ^^ ^^^ ^^^ ^^^
>operands : Expression[] | [Expression]
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^
>every : { <S extends Expression>(predicate: (value: Expression, index: number, array: Expression[]) => value is S, thisArg?: any): this is S[]; (predicate: (value: Expression, index: number, array: Expression[]) => unknown, thisArg?: any): boolean; } | { <S extends Expression>(predicate: (value: Expression, index: number, array: Expression[]) => value is S, thisArg?: any): this is S[]; (predicate: (value: Expression, index: number, array: Expression[]) => unknown, thisArg?: any): boolean; }
> : ^^^ ^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^ ^ ^^^ ^^^ ^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^ ^^ ^^^ ^^^ ^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^ ^ ^^^ ^^^ ^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^ ^^ ^^^ ^^^ ^^^
>operands.every : { <S extends Expression>(predicate: (value: Expression, index: number, array: Expression[]) => value is S, thisArg?: any): this is S[]; (predicate: (value: Expression, index: number, array: Expression[]) => unknown, thisArg?: any): boolean; }
> : ^^^ ^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^ ^ ^^^ ^^^ ^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^ ^^ ^^^ ^^^ ^^^
>operands : Expression[]
> : ^^^^^^^^^^^^
>every : { <S extends Expression>(predicate: (value: Expression, index: number, array: Expression[]) => value is S, thisArg?: any): this is S[]; (predicate: (value: Expression, index: number, array: Expression[]) => unknown, thisArg?: any): boolean; }
> : ^^^ ^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^ ^ ^^^ ^^^ ^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^ ^^ ^^^ ^^^ ^^^
>(child) => evaluate(child) : (child: Expression) => boolean
> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^
>child : Expression
Expand All @@ -76,8 +76,8 @@ export function evaluate(expression: Expression): boolean {
> : ^ ^^ ^^^^^
>operands[0] : Expression
> : ^^^^^^^^^^
>operands : Expression[] | [Expression]
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^
>operands : [Expression]
> : ^^^^^^^^^^^^
>0 : 0
> : ^
}
Expand Down
Loading
Loading