diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index bd2d5f99899bc..29846a33ec976 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -28684,6 +28684,20 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return symbol.isAssigned || false; } + // Check if a parameter or catch variable (or their bindings elements) is assigned anywhere + function isSomeSymbolAssigned(rootDeclaration: Node) { + Debug.assert(isVariableDeclaration(rootDeclaration) || isParameter(rootDeclaration)); + return isSomeSymbolAssignedWorker(rootDeclaration.name); + } + + function isSomeSymbolAssignedWorker(node: BindingName): boolean { + if (node.kind === SyntaxKind.Identifier) { + return isSymbolAssigned(getSymbolOfDeclaration(node.parent as Declaration)); + } + + return some(node.elements, e => e.kind !== SyntaxKind.OmittedExpression && isSomeSymbolAssignedWorker(e.name)); + } + function hasParentWithAssignmentsMarked(node: Node) { return !!findAncestor(node.parent, node => (isFunctionLike(node) || isCatchClause(node)) && !!(getNodeLinks(node).flags & NodeCheckFlags.AssignmentsMarked)); } @@ -28867,7 +28881,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const parentType = getTypeForBindingElementParent(parent, CheckMode.Normal); const parentTypeConstraint = parentType && mapType(parentType, getBaseConstraintOrType); links.flags &= ~NodeCheckFlags.InCheckIdentifier; - if (parentTypeConstraint && parentTypeConstraint.flags & TypeFlags.Union && !(rootDeclaration.kind === SyntaxKind.Parameter && isSymbolAssigned(symbol))) { + if (parentTypeConstraint && parentTypeConstraint.flags & TypeFlags.Union && !(rootDeclaration.kind === SyntaxKind.Parameter && isSomeSymbolAssigned(rootDeclaration))) { const pattern = declaration.parent; const narrowedType = getFlowTypeOfReference(pattern, parentTypeConstraint, parentTypeConstraint, /*flowContainer*/ undefined, location.flowNode); if (narrowedType.flags & TypeFlags.Never) { @@ -28907,7 +28921,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const contextualSignature = getContextualSignature(func); if (contextualSignature && contextualSignature.parameters.length === 1 && signatureHasRestParameter(contextualSignature)) { const restType = getReducedApparentType(instantiateType(getTypeOfSymbol(contextualSignature.parameters[0]), getInferenceContext(func)?.nonFixingMapper)); - if (restType.flags & TypeFlags.Union && everyType(restType, isTupleType) && !isSymbolAssigned(symbol)) { + if (restType.flags & TypeFlags.Union && everyType(restType, isTupleType) && !some(func.parameters, isSomeSymbolAssigned)) { const narrowedType = getFlowTypeOfReference(func, restType, restType, /*flowContainer*/ undefined, location.flowNode); const index = func.parameters.indexOf(declaration) - (getThisParameter(func) ? 1 : 0); return getIndexedAccessType(narrowedType, getNumberLiteralType(index)); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index f430e27b8539d..9078270ee340d 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5811,7 +5811,7 @@ export interface Symbol { /** @internal */ constEnumOnlyModule: boolean | undefined; // True if module contains only const enums or other modules with only const enums /** @internal */ isReferenced?: SymbolFlags; // True if the symbol is referenced elsewhere. Keeps track of the meaning of a reference in case a symbol is both a type parameter and parameter. /** @internal */ isReplaceableByMethod?: boolean; // Can this Javascript class property be replaced by a method symbol? - /** @internal */ isAssigned?: boolean; // True if the symbol is a parameter with assignments + /** @internal */ isAssigned?: boolean; // True if the symbol is a parameter with assignments /** @internal */ assignmentDeclarationMembers?: Map; // detected late-bound assignment declarations associated with the symbol } diff --git a/tests/baselines/reference/dependentDestructuredVariables.errors.txt b/tests/baselines/reference/dependentDestructuredVariables.errors.txt index 58fda3e63d75f..f6e2dd3d42745 100644 --- a/tests/baselines/reference/dependentDestructuredVariables.errors.txt +++ b/tests/baselines/reference/dependentDestructuredVariables.errors.txt @@ -443,4 +443,35 @@ dependentDestructuredVariables.ts(431,15): error TS2322: Type 'number' is not as !!! error TS2322: Type 'number' is not assignable to type 'never'. } } + + // https://github.com/microsoft/TypeScript/issues/56312 + + function parameterReassigned1([x, y]: [1, 2] | [3, 4]) { + if (Math.random()) { + x = 1; + } + if (y === 2) { + x; // 1 | 3 + } + } + + function parameterReassigned2([x, y]: [1, 2] | [3, 4]) { + if (Math.random()) { + y = 2; + } + if (y === 2) { + x; // 1 | 3 + } + } + + // https://github.com/microsoft/TypeScript/pull/56313#discussion_r1416482490 + + const parameterReassignedContextualRest1: (...args: [1, 2] | [3, 4]) => void = (x, y) => { + if (Math.random()) { + y = 2; + } + if (y === 2) { + x; // 1 | 3 + } + } \ No newline at end of file diff --git a/tests/baselines/reference/dependentDestructuredVariables.js b/tests/baselines/reference/dependentDestructuredVariables.js index ed1a7307e58b9..7d32808909bf7 100644 --- a/tests/baselines/reference/dependentDestructuredVariables.js +++ b/tests/baselines/reference/dependentDestructuredVariables.js @@ -434,6 +434,37 @@ function tooNarrow([x, y]: [1, 1] | [1, 2] | [1]) { const shouldNotBeOk: never = x; // Error } } + +// https://github.com/microsoft/TypeScript/issues/56312 + +function parameterReassigned1([x, y]: [1, 2] | [3, 4]) { + if (Math.random()) { + x = 1; + } + if (y === 2) { + x; // 1 | 3 + } +} + +function parameterReassigned2([x, y]: [1, 2] | [3, 4]) { + if (Math.random()) { + y = 2; + } + if (y === 2) { + x; // 1 | 3 + } +} + +// https://github.com/microsoft/TypeScript/pull/56313#discussion_r1416482490 + +const parameterReassignedContextualRest1: (...args: [1, 2] | [3, 4]) => void = (x, y) => { + if (Math.random()) { + y = 2; + } + if (y === 2) { + x; // 1 | 3 + } +} //// [dependentDestructuredVariables.js] @@ -766,6 +797,32 @@ function tooNarrow([x, y]) { const shouldNotBeOk = x; // Error } } +// https://github.com/microsoft/TypeScript/issues/56312 +function parameterReassigned1([x, y]) { + if (Math.random()) { + x = 1; + } + if (y === 2) { + x; // 1 | 3 + } +} +function parameterReassigned2([x, y]) { + if (Math.random()) { + y = 2; + } + if (y === 2) { + x; // 1 | 3 + } +} +// https://github.com/microsoft/TypeScript/pull/56313#discussion_r1416482490 +const parameterReassignedContextualRest1 = (x, y) => { + if (Math.random()) { + y = 2; + } + if (y === 2) { + x; // 1 | 3 + } +}; //// [dependentDestructuredVariables.d.ts] @@ -916,3 +973,6 @@ declare class Client { declare const bot: Client; declare function fz1([x, y]: [1, 2] | [3, 4] | [5]): void; declare function tooNarrow([x, y]: [1, 1] | [1, 2] | [1]): void; +declare function parameterReassigned1([x, y]: [1, 2] | [3, 4]): void; +declare function parameterReassigned2([x, y]: [1, 2] | [3, 4]): void; +declare const parameterReassignedContextualRest1: (...args: [1, 2] | [3, 4]) => void; diff --git a/tests/baselines/reference/dependentDestructuredVariables.symbols b/tests/baselines/reference/dependentDestructuredVariables.symbols index 8d4298cdb12c9..ae71b255d1a88 100644 --- a/tests/baselines/reference/dependentDestructuredVariables.symbols +++ b/tests/baselines/reference/dependentDestructuredVariables.symbols @@ -1100,3 +1100,71 @@ function tooNarrow([x, y]: [1, 1] | [1, 2] | [1]) { } } +// https://github.com/microsoft/TypeScript/issues/56312 + +function parameterReassigned1([x, y]: [1, 2] | [3, 4]) { +>parameterReassigned1 : Symbol(parameterReassigned1, Decl(dependentDestructuredVariables.ts, 432, 1)) +>x : Symbol(x, Decl(dependentDestructuredVariables.ts, 436, 31)) +>y : Symbol(y, Decl(dependentDestructuredVariables.ts, 436, 33)) + + if (Math.random()) { +>Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) +>Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) +>random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) + + x = 1; +>x : Symbol(x, Decl(dependentDestructuredVariables.ts, 436, 31)) + } + if (y === 2) { +>y : Symbol(y, Decl(dependentDestructuredVariables.ts, 436, 33)) + + x; // 1 | 3 +>x : Symbol(x, Decl(dependentDestructuredVariables.ts, 436, 31)) + } +} + +function parameterReassigned2([x, y]: [1, 2] | [3, 4]) { +>parameterReassigned2 : Symbol(parameterReassigned2, Decl(dependentDestructuredVariables.ts, 443, 1)) +>x : Symbol(x, Decl(dependentDestructuredVariables.ts, 445, 31)) +>y : Symbol(y, Decl(dependentDestructuredVariables.ts, 445, 33)) + + if (Math.random()) { +>Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) +>Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) +>random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) + + y = 2; +>y : Symbol(y, Decl(dependentDestructuredVariables.ts, 445, 33)) + } + if (y === 2) { +>y : Symbol(y, Decl(dependentDestructuredVariables.ts, 445, 33)) + + x; // 1 | 3 +>x : Symbol(x, Decl(dependentDestructuredVariables.ts, 445, 31)) + } +} + +// https://github.com/microsoft/TypeScript/pull/56313#discussion_r1416482490 + +const parameterReassignedContextualRest1: (...args: [1, 2] | [3, 4]) => void = (x, y) => { +>parameterReassignedContextualRest1 : Symbol(parameterReassignedContextualRest1, Decl(dependentDestructuredVariables.ts, 456, 5)) +>args : Symbol(args, Decl(dependentDestructuredVariables.ts, 456, 43)) +>x : Symbol(x, Decl(dependentDestructuredVariables.ts, 456, 80)) +>y : Symbol(y, Decl(dependentDestructuredVariables.ts, 456, 82)) + + if (Math.random()) { +>Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) +>Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) +>random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) + + y = 2; +>y : Symbol(y, Decl(dependentDestructuredVariables.ts, 456, 82)) + } + if (y === 2) { +>y : Symbol(y, Decl(dependentDestructuredVariables.ts, 456, 82)) + + x; // 1 | 3 +>x : Symbol(x, Decl(dependentDestructuredVariables.ts, 456, 80)) + } +} + diff --git a/tests/baselines/reference/dependentDestructuredVariables.types b/tests/baselines/reference/dependentDestructuredVariables.types index 25f18a848374b..0480bb1631c62 100644 --- a/tests/baselines/reference/dependentDestructuredVariables.types +++ b/tests/baselines/reference/dependentDestructuredVariables.types @@ -1263,3 +1263,87 @@ function tooNarrow([x, y]: [1, 1] | [1, 2] | [1]) { } } +// https://github.com/microsoft/TypeScript/issues/56312 + +function parameterReassigned1([x, y]: [1, 2] | [3, 4]) { +>parameterReassigned1 : ([x, y]: [1, 2] | [3, 4]) => void +>x : 1 | 3 +>y : 2 | 4 + + if (Math.random()) { +>Math.random() : number +>Math.random : () => number +>Math : Math +>random : () => number + + x = 1; +>x = 1 : 1 +>x : 1 | 3 +>1 : 1 + } + if (y === 2) { +>y === 2 : boolean +>y : 2 | 4 +>2 : 2 + + x; // 1 | 3 +>x : 1 | 3 + } +} + +function parameterReassigned2([x, y]: [1, 2] | [3, 4]) { +>parameterReassigned2 : ([x, y]: [1, 2] | [3, 4]) => void +>x : 1 | 3 +>y : 2 | 4 + + if (Math.random()) { +>Math.random() : number +>Math.random : () => number +>Math : Math +>random : () => number + + y = 2; +>y = 2 : 2 +>y : 2 | 4 +>2 : 2 + } + if (y === 2) { +>y === 2 : boolean +>y : 2 | 4 +>2 : 2 + + x; // 1 | 3 +>x : 1 | 3 + } +} + +// https://github.com/microsoft/TypeScript/pull/56313#discussion_r1416482490 + +const parameterReassignedContextualRest1: (...args: [1, 2] | [3, 4]) => void = (x, y) => { +>parameterReassignedContextualRest1 : (...args: [1, 2] | [3, 4]) => void +>args : [1, 2] | [3, 4] +>(x, y) => { if (Math.random()) { y = 2; } if (y === 2) { x; // 1 | 3 }} : (x: 1 | 3, y: 2 | 4) => void +>x : 1 | 3 +>y : 2 | 4 + + if (Math.random()) { +>Math.random() : number +>Math.random : () => number +>Math : Math +>random : () => number + + y = 2; +>y = 2 : 2 +>y : 2 | 4 +>2 : 2 + } + if (y === 2) { +>y === 2 : boolean +>y : 2 | 4 +>2 : 2 + + x; // 1 | 3 +>x : 1 | 3 + } +} + diff --git a/tests/baselines/reference/narrowRefinedConstLikeParameterBIndingElementNameInInnerScope.symbols b/tests/baselines/reference/narrowRefinedConstLikeParameterBIndingElementNameInInnerScope.symbols new file mode 100644 index 0000000000000..7c22602f09ac9 --- /dev/null +++ b/tests/baselines/reference/narrowRefinedConstLikeParameterBIndingElementNameInInnerScope.symbols @@ -0,0 +1,24 @@ +//// [tests/cases/compiler/narrowRefinedConstLikeParameterBIndingElementNameInInnerScope.ts] //// + +=== narrowRefinedConstLikeParameterBIndingElementNameInInnerScope.ts === +function ff({ a, b }: { a: string | undefined, b: () => void }) { +>ff : Symbol(ff, Decl(narrowRefinedConstLikeParameterBIndingElementNameInInnerScope.ts, 0, 0)) +>a : Symbol(a, Decl(narrowRefinedConstLikeParameterBIndingElementNameInInnerScope.ts, 0, 13)) +>b : Symbol(b, Decl(narrowRefinedConstLikeParameterBIndingElementNameInInnerScope.ts, 0, 16)) +>a : Symbol(a, Decl(narrowRefinedConstLikeParameterBIndingElementNameInInnerScope.ts, 0, 23)) +>b : Symbol(b, Decl(narrowRefinedConstLikeParameterBIndingElementNameInInnerScope.ts, 0, 46)) + + if (a !== undefined) { +>a : Symbol(a, Decl(narrowRefinedConstLikeParameterBIndingElementNameInInnerScope.ts, 0, 13)) +>undefined : Symbol(undefined) + + b = () => { +>b : Symbol(b, Decl(narrowRefinedConstLikeParameterBIndingElementNameInInnerScope.ts, 0, 16)) + + const x: string = a; +>x : Symbol(x, Decl(narrowRefinedConstLikeParameterBIndingElementNameInInnerScope.ts, 3, 11)) +>a : Symbol(a, Decl(narrowRefinedConstLikeParameterBIndingElementNameInInnerScope.ts, 0, 13)) + } + } +} + diff --git a/tests/baselines/reference/narrowRefinedConstLikeParameterBIndingElementNameInInnerScope.types b/tests/baselines/reference/narrowRefinedConstLikeParameterBIndingElementNameInInnerScope.types new file mode 100644 index 0000000000000..16ddc0c8ad1ab --- /dev/null +++ b/tests/baselines/reference/narrowRefinedConstLikeParameterBIndingElementNameInInnerScope.types @@ -0,0 +1,27 @@ +//// [tests/cases/compiler/narrowRefinedConstLikeParameterBIndingElementNameInInnerScope.ts] //// + +=== narrowRefinedConstLikeParameterBIndingElementNameInInnerScope.ts === +function ff({ a, b }: { a: string | undefined, b: () => void }) { +>ff : ({ a, b }: { a: string | undefined; b: () => void;}) => void +>a : string | undefined +>b : () => void +>a : string | undefined +>b : () => void + + if (a !== undefined) { +>a !== undefined : boolean +>a : string | undefined +>undefined : undefined + + b = () => { +>b = () => { const x: string = a; } : () => void +>b : () => void +>() => { const x: string = a; } : () => void + + const x: string = a; +>x : string +>a : string + } + } +} + diff --git a/tests/cases/compiler/narrowRefinedConstLikeParameterBIndingElementNameInInnerScope.ts b/tests/cases/compiler/narrowRefinedConstLikeParameterBIndingElementNameInInnerScope.ts new file mode 100644 index 0000000000000..3f88e085c31cc --- /dev/null +++ b/tests/cases/compiler/narrowRefinedConstLikeParameterBIndingElementNameInInnerScope.ts @@ -0,0 +1,10 @@ +// @strict: true +// @noEmit: true + +function ff({ a, b }: { a: string | undefined, b: () => void }) { + if (a !== undefined) { + b = () => { + const x: string = a; + } + } +} diff --git a/tests/cases/conformance/controlFlow/dependentDestructuredVariables.ts b/tests/cases/conformance/controlFlow/dependentDestructuredVariables.ts index 7b18bec96103b..67bce5559e9cf 100644 --- a/tests/cases/conformance/controlFlow/dependentDestructuredVariables.ts +++ b/tests/cases/conformance/controlFlow/dependentDestructuredVariables.ts @@ -436,3 +436,34 @@ function tooNarrow([x, y]: [1, 1] | [1, 2] | [1]) { const shouldNotBeOk: never = x; // Error } } + +// https://github.com/microsoft/TypeScript/issues/56312 + +function parameterReassigned1([x, y]: [1, 2] | [3, 4]) { + if (Math.random()) { + x = 1; + } + if (y === 2) { + x; // 1 | 3 + } +} + +function parameterReassigned2([x, y]: [1, 2] | [3, 4]) { + if (Math.random()) { + y = 2; + } + if (y === 2) { + x; // 1 | 3 + } +} + +// https://github.com/microsoft/TypeScript/pull/56313#discussion_r1416482490 + +const parameterReassignedContextualRest1: (...args: [1, 2] | [3, 4]) => void = (x, y) => { + if (Math.random()) { + y = 2; + } + if (y === 2) { + x; // 1 | 3 + } +}