Skip to content

Commit bbda1ed

Browse files
committedDec 7, 2023
Keep narrowing non-reassigned names coming from parameters
1 parent fc368df commit bbda1ed

6 files changed

+91
-13
lines changed
 

‎src/compiler/checker.ts

+27-11
Original file line numberDiff line numberDiff line change
@@ -27393,7 +27393,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2739327393
case SyntaxKind.Identifier:
2739427394
if (!isThisInTypeQuery(node)) {
2739527395
const symbol = getResolvedSymbol(node as Identifier);
27396-
return isConstantVariable(symbol) || isParameterOrCatchClauseVariable(symbol) && !isSomeSymbolAssigned(getRootDeclaration(symbol.valueDeclaration!));
27396+
return isConstantVariable(symbol) || isParameterOrCatchClauseVariable(symbol) && !isSymbolAssigned(symbol);
2739727397
}
2739827398
break;
2739927399
case SyntaxKind.PropertyAccessExpression:
@@ -28668,34 +28668,51 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2866828668
node.kind === SyntaxKind.PropertyDeclaration)!;
2866928669
}
2867028670

28671-
// Check if a parameter or catch variable (or their bindings elements) is assigned anywhere
28672-
function isSomeSymbolAssigned(rootDeclaration: Node) {
28673-
const parent = rootDeclaration.parent;
28671+
// Check if a parameter or catch variable is assigned anywhere
28672+
function isSymbolAssigned(symbol: Symbol) {
28673+
if (!symbol.valueDeclaration) {
28674+
return false;
28675+
}
28676+
const parent = getRootDeclaration(symbol.valueDeclaration).parent;
2867428677
const links = getNodeLinks(parent);
2867528678
if (!(links.flags & NodeCheckFlags.AssignmentsMarked)) {
2867628679
links.flags |= NodeCheckFlags.AssignmentsMarked;
2867728680
if (!hasParentWithAssignmentsMarked(parent)) {
2867828681
markNodeAssignments(parent);
2867928682
}
2868028683
}
28681-
return !!getNodeLinks(rootDeclaration).someSymbolAssigned;
28684+
return symbol.isAssigned || false;
28685+
}
28686+
28687+
// Check if a parameter or catch variable (or their bindings elements) is assigned anywhere
28688+
function isSomeSymbolAssigned(rootDeclaration: Node) {
28689+
Debug.assert(isVariableDeclaration(rootDeclaration) || isParameter(rootDeclaration));
28690+
return isSomeSymbolAssignedWorker(rootDeclaration.name);
28691+
}
28692+
28693+
function isSomeSymbolAssignedWorker(node: BindingName): boolean {
28694+
if (node.kind === SyntaxKind.Identifier) {
28695+
return isSymbolAssigned(getSymbolOfDeclaration(node.parent as Declaration));
28696+
}
28697+
28698+
return some(node.elements, e => e.kind !== SyntaxKind.OmittedExpression && isSomeSymbolAssignedWorker(e.name));
2868228699
}
2868328700

2868428701
function hasParentWithAssignmentsMarked(node: Node) {
2868528702
return !!findAncestor(node.parent, node => (isFunctionLike(node) || isCatchClause(node)) && !!(getNodeLinks(node).flags & NodeCheckFlags.AssignmentsMarked));
2868628703
}
2868728704

28688-
function markNodeAssignments(node: Node): true | undefined {
28705+
function markNodeAssignments(node: Node) {
2868928706
if (node.kind === SyntaxKind.Identifier) {
2869028707
if (isAssignmentTarget(node)) {
2869128708
const symbol = getResolvedSymbol(node as Identifier);
2869228709
if (isParameterOrCatchClauseVariable(symbol)) {
28693-
return getNodeLinks(getRootDeclaration(symbol.valueDeclaration!)).someSymbolAssigned = true;
28710+
symbol.isAssigned = true;
2869428711
}
2869528712
}
2869628713
}
2869728714
else {
28698-
return forEachChild(node, markNodeAssignments);
28715+
forEachChild(node, markNodeAssignments);
2869928716
}
2870028717
}
2870128718

@@ -29045,8 +29062,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2904529062
// The declaration container is the innermost function that encloses the declaration of the variable
2904629063
// or parameter. The flow container is the innermost function starting with which we analyze the control
2904729064
// flow graph to determine the control flow based type.
29048-
const rootDeclaration = getRootDeclaration(declaration);
29049-
const isParameter = rootDeclaration.kind === SyntaxKind.Parameter;
29065+
const isParameter = getRootDeclaration(declaration).kind === SyntaxKind.Parameter;
2905029066
const declarationContainer = getControlFlowContainer(declaration);
2905129067
let flowContainer = getControlFlowContainer(node);
2905229068
const isOuterVariable = flowContainer !== declarationContainer;
@@ -29060,7 +29076,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2906029076
while (
2906129077
flowContainer !== declarationContainer && (flowContainer.kind === SyntaxKind.FunctionExpression ||
2906229078
flowContainer.kind === SyntaxKind.ArrowFunction || isObjectLiteralOrClassExpressionMethodOrAccessor(flowContainer)) &&
29063-
(isConstantVariable(localOrExportSymbol) && type !== autoArrayType || isParameter && !isSomeSymbolAssigned(rootDeclaration))
29079+
(isConstantVariable(localOrExportSymbol) && type !== autoArrayType || isParameter && !isSymbolAssigned(localOrExportSymbol))
2906429080
) {
2906529081
flowContainer = getControlFlowContainer(flowContainer);
2906629082
}

‎src/compiler/types.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -5811,6 +5811,7 @@ export interface Symbol {
58115811
/** @internal */ constEnumOnlyModule: boolean | undefined; // True if module contains only const enums or other modules with only const enums
58125812
/** @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.
58135813
/** @internal */ isReplaceableByMethod?: boolean; // Can this Javascript class property be replaced by a method symbol?
5814+
/** @internal */ isAssigned?: boolean; // True if the symbol is a parameter with assignments
58145815
/** @internal */ assignmentDeclarationMembers?: Map<number, Declaration>; // detected late-bound assignment declarations associated with the symbol
58155816
}
58165817

@@ -6049,7 +6050,6 @@ export interface NodeLinks {
60496050
parameterInitializerContainsUndefined?: boolean; // True if this is a parameter declaration whose type annotation contains "undefined".
60506051
fakeScopeForSignatureDeclaration?: "params" | "typeParams"; // If present, this is a fake scope injected into an enclosing declaration chain.
60516052
assertionExpressionType?: Type; // Cached type of the expression of a type assertion
6052-
someSymbolAssigned?: boolean; // True if any symbol declared in a parameter or catch clause is being assigned to
60536053
}
60546054

60556055
/** @internal */

‎src/compiler/utilities.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -5278,7 +5278,7 @@ export function isPushOrUnshiftIdentifier(node: Identifier) {
52785278
*
52795279
* @internal
52805280
*/
5281-
export function isParameterDeclaration(node: Declaration): boolean {
5281+
export function isParameterDeclaration(node: Declaration): node is ParameterDeclaration {
52825282
const root = getRootDeclaration(node);
52835283
return root.kind === SyntaxKind.Parameter;
52845284
}
@@ -8155,6 +8155,7 @@ function Symbol(this: Symbol, flags: SymbolFlags, name: __String) {
81558155
this.exportSymbol = undefined;
81568156
this.constEnumOnlyModule = undefined;
81578157
this.isReferenced = undefined;
8158+
this.isAssigned = undefined;
81588159
(this as any).links = undefined; // used by TransientSymbol
81598160
}
81608161

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//// [tests/cases/compiler/narrowRefinedConstLikeParameterBIndingElementNameInInnerScope.ts] ////
2+
3+
=== narrowRefinedConstLikeParameterBIndingElementNameInInnerScope.ts ===
4+
function ff({ a, b }: { a: string | undefined, b: () => void }) {
5+
>ff : Symbol(ff, Decl(narrowRefinedConstLikeParameterBIndingElementNameInInnerScope.ts, 0, 0))
6+
>a : Symbol(a, Decl(narrowRefinedConstLikeParameterBIndingElementNameInInnerScope.ts, 0, 13))
7+
>b : Symbol(b, Decl(narrowRefinedConstLikeParameterBIndingElementNameInInnerScope.ts, 0, 16))
8+
>a : Symbol(a, Decl(narrowRefinedConstLikeParameterBIndingElementNameInInnerScope.ts, 0, 23))
9+
>b : Symbol(b, Decl(narrowRefinedConstLikeParameterBIndingElementNameInInnerScope.ts, 0, 46))
10+
11+
if (a !== undefined) {
12+
>a : Symbol(a, Decl(narrowRefinedConstLikeParameterBIndingElementNameInInnerScope.ts, 0, 13))
13+
>undefined : Symbol(undefined)
14+
15+
b = () => {
16+
>b : Symbol(b, Decl(narrowRefinedConstLikeParameterBIndingElementNameInInnerScope.ts, 0, 16))
17+
18+
const x: string = a;
19+
>x : Symbol(x, Decl(narrowRefinedConstLikeParameterBIndingElementNameInInnerScope.ts, 3, 11))
20+
>a : Symbol(a, Decl(narrowRefinedConstLikeParameterBIndingElementNameInInnerScope.ts, 0, 13))
21+
}
22+
}
23+
}
24+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//// [tests/cases/compiler/narrowRefinedConstLikeParameterBIndingElementNameInInnerScope.ts] ////
2+
3+
=== narrowRefinedConstLikeParameterBIndingElementNameInInnerScope.ts ===
4+
function ff({ a, b }: { a: string | undefined, b: () => void }) {
5+
>ff : ({ a, b }: { a: string | undefined; b: () => void;}) => void
6+
>a : string | undefined
7+
>b : () => void
8+
>a : string | undefined
9+
>b : () => void
10+
11+
if (a !== undefined) {
12+
>a !== undefined : boolean
13+
>a : string | undefined
14+
>undefined : undefined
15+
16+
b = () => {
17+
>b = () => { const x: string = a; } : () => void
18+
>b : () => void
19+
>() => { const x: string = a; } : () => void
20+
21+
const x: string = a;
22+
>x : string
23+
>a : string
24+
}
25+
}
26+
}
27+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// @strict: true
2+
// @noEmit: true
3+
4+
function ff({ a, b }: { a: string | undefined, b: () => void }) {
5+
if (a !== undefined) {
6+
b = () => {
7+
const x: string = a;
8+
}
9+
}
10+
}

0 commit comments

Comments
 (0)