Merged
Show file tree
Hide file tree
Changes from all commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
ecf36a7
error on variables that are used but never uninitialized
ZzzenSep 27, 2023
8416f98
format
ZzzenSep 27, 2023
54183fd
Merge remote-tracking branch 'origin/main' into check-uninitialized-v…
ZzzenSep 27, 2023
ff025b3
fix tests
ZzzenSep 27, 2023
7bb240f
fix ForInOrOfStatement
ZzzenSep 28, 2023
8d814cd
reuse `isAssigned` flag
ZzzenSep 28, 2023
e897198
Merge branch 'main' into check-uninitialized-variables
DanielRosenwasserApr 5, 2024
5fbd69d
Merge remote-tracking branch 'origin/main' into check-uninitialized-v…
ZzzenJul 17, 2024
e1262a6
add tests
ZzzenJul 18, 2024
2942ade
fix compoud assignment
ZzzenJul 19, 2024
23be105
update error message
ZzzenJul 19, 2024
8e4e266
Merge remote-tracking branch 'origin/main' into check-uninitialized-v…
ZzzenJul 20, 2024
d32f696
update baseline
ZzzenJul 20, 2024
b3edbf0
ignore var declarations
ZzzenJul 21, 2024
775670c
fix self check
ZzzenJul 21, 2024
158d0f3
cache isDefinitelyAssigned
ZzzenJul 23, 2024
6bfa970
Merge remote-tracking branch 'origin/main' into check-uninitialized-v…
ZzzenJul 23, 2024
66f2210
update error code
ZzzenJul 23, 2024
44ce876
format
ZzzenJul 23, 2024
f9a8207
update comments and ignore constant variables
ZzzenJul 26, 2024
46b735c
Merge remote-tracking branch 'origin/main' into check-uninitialized-v…
ZzzenJul 27, 2024
1aabd49
reset impl
ZzzenJul 27, 2024
45c0011
refactor
ZzzenJul 28, 2024
f0b637f
narrow function scoped variables
ZzzenJul 28, 2024
bf4bb51
inline variables
ZzzenAug 4, 2024
10e2cf8
ignore var variables
ZzzenAug 8, 2024
6c85362
update test
ZzzenAug 9, 2024
b930faa
update code
ZzzenAug 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Failed to load files.
Original file line numberDiff line numberDiff line change
Expand Up@@ -29504,7 +29504,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
node.kind === SyntaxKind.PropertyDeclaration)!;
}

// Check if a parameter or catch variable is assigned anywhere
// Check if a parameter, catch variable, or mutable local variable is assigned anywhere definitely
function isSymbolAssignedDefinitely(symbol: Symbol) {
if (symbol.lastAssignmentPos !== undefined) {
return symbol.lastAssignmentPos < 0;
}
return isSymbolAssigned(symbol) && symbol.lastAssignmentPos !== undefined && symbol.lastAssignmentPos < 0;
}

// Check if a parameter, catch variable, or mutable local variable is assigned anywhere
function isSymbolAssigned(symbol: Symbol) {
return !isPastLastAssignment(symbol, /*location*/ undefined);
}
Expand All@@ -29523,7 +29531,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
markNodeAssignments(parent);
}
}
return !symbol.lastAssignmentPos || location && symbol.lastAssignmentPos < location.pos;
return !symbol.lastAssignmentPos || location && Math.abs(symbol.lastAssignmentPos) < location.pos;
}

// Check if a parameter or catch variable (or their bindings elements) is assigned anywhere
Expand DownExpand Up@@ -29557,12 +29565,19 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
function markNodeAssignments(node: Node) {
switch (node.kind) {
case SyntaxKind.Identifier:
if (isAssignmentTarget(node)) {
const assigmentTarget = getAssignmentTargetKind(node);
if (assigmentTarget !== AssignmentKind.None) {
const symbol = getResolvedSymbol(node as Identifier);
if (isParameterOrMutableLocalVariable(symbol) && symbol.lastAssignmentPos !== Number.MAX_VALUE) {
const referencingFunction = findAncestor(node, isFunctionOrSourceFile);
const declaringFunction = findAncestor(symbol.valueDeclaration, isFunctionOrSourceFile);
symbol.lastAssignmentPos = referencingFunction === declaringFunction ? extendAssignmentPosition(node, symbol.valueDeclaration!) : Number.MAX_VALUE;
const hasDefiniteAssignment = assigmentTarget === AssignmentKind.Definite || (symbol.lastAssignmentPos !== undefined && symbol.lastAssignmentPos < 0);
if (isParameterOrMutableLocalVariable(symbol)) {
if (symbol.lastAssignmentPos === undefined || Math.abs(symbol.lastAssignmentPos) !== Number.MAX_VALUE) {
const referencingFunction = findAncestor(node, isFunctionOrSourceFile);
const declaringFunction = findAncestor(symbol.valueDeclaration, isFunctionOrSourceFile);
symbol.lastAssignmentPos = referencingFunction === declaringFunction ? extendAssignmentPosition(node, symbol.valueDeclaration!) : Number.MAX_VALUE;
}
if (hasDefiniteAssignment && symbol.lastAssignmentPos > 0) {
symbol.lastAssignmentPos *= -1;
}
}
}
return;
Expand All@@ -29572,7 +29587,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (!(node as ExportSpecifier).isTypeOnly && !exportDeclaration.isTypeOnly && !exportDeclaration.moduleSpecifier && name.kind !== SyntaxKind.StringLiteral) {
const symbol = resolveEntityName(name, SymbolFlags.Value, /*ignoreErrors*/ true, /*dontResolveAlias*/ true);
if (symbol && isParameterOrMutableLocalVariable(symbol)) {
symbol.lastAssignmentPos = Number.MAX_VALUE;
const sign = symbol.lastAssignmentPos !== undefined && symbol.lastAssignmentPos < 0 ? -1 : 1;
symbol.lastAssignmentPos = sign * Number.MAX_VALUE;
}
}
return;
Expand DownExpand Up@@ -30316,6 +30332,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {

const localOrExportSymbol = getExportSymbolOfValueSymbolIfExported(symbol);
let declaration = localOrExportSymbol.valueDeclaration;
const immediateDeclaration = declaration;

// If the identifier is declared in a binding pattern for which we're currently computing the implied type and the
// reference occurs with the same binding pattern, return the non-inferrable any type. This for example occurs in
Expand DownExpand Up@@ -30405,7 +30422,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// We only look for uninitialized variables in strict null checking mode, and only when we can analyze
// the entire control flow graph from the variable's declaration (i.e. when the flow container and
// declaration container are the same).
const assumeInitialized = isParameter || isAlias || isOuterVariable || isSpreadDestructuringAssignmentTarget || isModuleExports || isSameScopedBindingElement(node, declaration) ||
const isNeverInitialized = immediateDeclaration && isVariableDeclaration(immediateDeclaration) && !immediateDeclaration.initializer && !immediateDeclaration.exclamationToken && isMutableLocalVariableDeclaration(immediateDeclaration) && !isSymbolAssignedDefinitely(symbol);
const assumeInitialized = isParameter || isAlias ||
(isOuterVariable && !isNeverInitialized) ||
isSpreadDestructuringAssignmentTarget || isModuleExports || isSameScopedBindingElement(node, declaration) ||
type !== autoType && type !== autoArrayType && (!strictNullChecks || (type.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Void)) !== 0 ||
isInTypeQuery(node) || isInAmbientOrTypeNode(node) || node.parent.kind === SyntaxKind.ExportSpecifier) ||
node.parent.kind === SyntaxKind.NonNullExpression ||
Expand DownExpand Up@@ -43264,7 +43284,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
case SyntaxKind.MethodDeclaration:
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
if (node.body) { // Don't report unused parameters in overloads
// Only report unused parameters on the implementation, not overloads.
if (node.body) {
checkUnusedLocalsAndParameters(node, addDiagnostic);
}
checkUnusedTypeParameters(node, addDiagnostic);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -228,6 +228,7 @@ import {
} from "../_namespaces/ts.js";

const enum ClassPropertySubstitutionFlags {
None = 0,
/**
* Enables substitutions for class expressions with static fields
* which have initializers that reference the class name.
Expand DownExpand Up@@ -401,7 +402,7 @@ export function transformClassFields(context: TransformationContext): (x: Source
context.onEmitNode = onEmitNode;

let shouldTransformPrivateStaticElementsInFile = false;
let enabledSubstitutions: ClassPropertySubstitutionFlags;
let enabledSubstitutions = ClassPropertySubstitutionFlags.None;

let classAliases: Identifier[];

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -218,6 +218,7 @@ import {
} from "../_namespaces/ts.js";

const enum ES2015SubstitutionFlags {
None = 0,
/** Enables substitutions for captured `this` */
CapturedThis = 1 << 0,
/** Enables substitutions for block-scoped bindings. */
Expand DownExpand Up@@ -523,7 +524,7 @@ export function transformES2015(context: TransformationContext): (x: SourceFile
* They are persisted between each SourceFile transformation and should not
* be reset.
*/
let enabledSubstitutions: ES2015SubstitutionFlags;
let enabledSubstitutions = ES2015SubstitutionFlags.None;

return chainBundle(context, transformSourceFile);

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -104,6 +104,7 @@ import {
type SuperContainer = ClassDeclaration | MethodDeclaration | GetAccessorDeclaration | SetAccessorDeclaration | ConstructorDeclaration;

const enum ES2017SubstitutionFlags {
None = 0,
/** Enables substitutions for async methods with `super` calls. */
AsyncMethodsWithSuper = 1 << 0,
}
Expand DownExpand Up@@ -132,7 +133,7 @@ export function transformES2017(context: TransformationContext): (x: SourceFile
* Keeps track of whether expression substitution has been enabled for specific edge cases.
* They are persisted between each SourceFile transformation and should not be reset.
*/
let enabledSubstitutions: ES2017SubstitutionFlags;
let enabledSubstitutions = ES2017SubstitutionFlags.None;

/**
* This keeps track of containers where `super` is valid, for use with
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -209,6 +209,7 @@ import {
const USE_NEW_TYPE_METADATA_FORMAT = false;

const enum TypeScriptSubstitutionFlags {
None = 0,
/** Enables substitutions for namespace exports. */
NamespaceExports = 1 << 1,
/* Enables substitutions for unqualified enum members */
Expand DownExpand Up@@ -272,7 +273,7 @@ export function transformTypeScript(context: TransformationContext) {
* Keeps track of whether expression substitution has been enabled for specific edge cases.
* They are persisted between each SourceFile transformation and should not be reset.
*/
let enabledSubstitutions: TypeScriptSubstitutionFlags;
let enabledSubstitutions = TypeScriptSubstitutionFlags.None;

/**
* Keeps track of whether we are within any containing namespaces when performing
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -5944,7 +5944,7 @@ export interface Symbol {
/** @internal */ exportSymbol?: Symbol; // Exported symbol associated with this 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 */ lastAssignmentPos?: number; // Source position of last node that assigns value to symbol
/** @internal */ lastAssignmentPos?: number; // Source position of last node that assigns value to symbol. Negative if it is assigned anywhere definitely
/** @internal */ isReplaceableByMethod?: boolean; // Can this Javascript class property be replaced by a method symbol?
/** @internal */ assignmentDeclarationMembers?: Map<number, Declaration>; // detected late-bound assignment declarations associated with the symbol
}
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -153,6 +153,8 @@ import {
forEachChild,
forEachChildRecursively,
ForInOrOfStatement,
ForInStatement,
ForOfStatement,
ForStatement,
FunctionBody,
FunctionDeclaration,
Expand DownExpand Up@@ -7905,6 +7907,9 @@ function accessKind(node: Node): AccessKind {
return node === (parent as ShorthandPropertyAssignment).objectAssignmentInitializer ? AccessKind.Read : accessKind(parent.parent);
case SyntaxKind.ArrayLiteralExpression:
return accessKind(parent);
case SyntaxKind.ForInStatement:
case SyntaxKind.ForOfStatement:
return node === (parent as ForInStatement | ForOfStatement).initializer ? AccessKind.Write : AccessKind.Read;
default:
return AccessKind.Read;
}
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,7 +13,7 @@
// for (<|var { [|property1|]: p1 } of elems|>) {
// }
// var p2;
// for (<|{ [|{| isWriteAccess: true |}property1|] : p2 } of elems|>) {
// for (<|{ [|property1|] : p2 } of elems|>) {
// }

// === Definitions ===
Expand DownExpand Up@@ -94,7 +94,7 @@
// for (<|var { [|property1|]: p1 } of elems|>) {
// }
// var p2;
// for (<|{ [|{| isWriteAccess: true |}property1|] : p2 } of elems|>) {
// for (<|{ [|property1|] : p2 } of elems|>) {
// }

// === Definitions ===
Expand DownExpand Up@@ -180,7 +180,7 @@
// for (<|var { /*FIND ALL REFS*/[|property1|]: p1 } of elems|>) {
// }
// var p2;
// for (<|{ [|{| isWriteAccess: true |}property1|] : p2 } of elems|>) {
// for (<|{ [|property1|] : p2 } of elems|>) {
// }

// === Definitions ===
Expand DownExpand Up@@ -270,7 +270,7 @@
// for (<|var { [|property1|]: p1 } of elems|>) {
// }
// var p2;
// for (<|{ /*FIND ALL REFS*/[|{| isWriteAccess: true, isDefinition: true |}property1|] : p2 } of elems|>) {
// for (<|{ /*FIND ALL REFS*/[|{| isDefinition: true |}property1|] : p2 } of elems|>) {
// }

// === Definitions ===
Expand DownExpand Up@@ -358,7 +358,7 @@
// for (<|var { [|{| defId: 0 |}property1|]: p1 } of elems|>) {
// }
// var p2;
// for (<|{ [|{| defId: 0, isWriteAccess: true |}property1|] : p2 } of elems|>) {
// for (<|{ [|{| defId: 0 |}property1|] : p2 } of elems|>) {
// }

// === Definitions ===
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
narrowingPastLastAssignment.ts(88,9): error TS7034: Variable 'x' implicitly has type 'any' in some locations where its type cannot be determined.
narrowingPastLastAssignment.ts(90,20): error TS7005: Variable 'x' implicitly has an 'any' type.
narrowingPastLastAssignment.ts(161,9): error TS18048: 'foo' is possibly 'undefined'.


==== narrowingPastLastAssignment.ts (2 errors) ====
==== narrowingPastLastAssignment.ts (3 errors) ====
function action(f: Function) {}

// Narrowings are preserved in closures created past last assignment
Expand DownExpand Up@@ -160,4 +161,15 @@ narrowingPastLastAssignment.ts(90,20): error TS7005: Variable 'x' implicitly has
}
values.forEach(v => foo.push(v));
}


function f13() {
// Test for captured 'var' declaration (as opposed to parameters, let, const).
var foo: string | undefined;
foo = '';

return () => {
foo.toLocaleLowerCase();
~~~
!!! error TS18048: 'foo' is possibly 'undefined'.
}
}
Loading