From 0fbf36c2fd86b010d043f9d3b9933f02628fdaad Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 18 Nov 2017 11:32:17 -0800 Subject: [PATCH] Add definite assignment checks for property accesses in constructor body --- src/compiler/checker.ts | 29 ++++++++++++++++++++++++++-- src/compiler/diagnosticMessages.json | 4 ++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index bb5ea71548..dc1bab72c3 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15604,7 +15604,26 @@ namespace ts { prop && !(prop.flags & (SymbolFlags.Variable | SymbolFlags.Property | SymbolFlags.Accessor)) && !(prop.flags & SymbolFlags.Method && propType.flags & TypeFlags.Union)) { return propType; } - const flowType = getFlowTypeOfReference(node, propType); + // If strict null checks and strict property initialization checks are enabled, if we have + // a this.xxx property access, if the property is an instance property without an initializer, + // and if we are in a constructor of the same class as the property declaration, assume that + // the property is uninitialized at the top of the control flow. + let assumeUninitialized = false; + if (strictNullChecks && strictPropertyInitialization && left.kind === SyntaxKind.ThisKeyword) { + const declaration = prop && prop.valueDeclaration; + if (declaration && isInstancePropertyWithoutInitializer(declaration)) { + const flowContainer = getControlFlowContainer(node); + if (flowContainer.kind === SyntaxKind.Constructor && flowContainer.parent === declaration.parent) { + assumeUninitialized = true; + } + } + } + const flowType = getFlowTypeOfReference(node, propType, assumeUninitialized ? getOptionalType(propType) : propType); + if (assumeUninitialized && !(getFalsyFlags(propType) & TypeFlags.Undefined) && getFalsyFlags(flowType) & TypeFlags.Undefined) { + error(right, Diagnostics.Property_0_is_used_before_being_assigned, symbolToString(prop)); + // Return the declared type to reduce follow-on errors + return propType; + } return assignmentKind ? getBaseTypeOfLiteralType(flowType) : flowType; } @@ -22633,7 +22652,7 @@ namespace ts { } const constructor = findConstructorDeclaration(node); for (const member of node.members) { - if (member.kind === SyntaxKind.PropertyDeclaration && !hasModifier(member, ModifierFlags.Static | ModifierFlags.Abstract) && !(member).initializer) { + if (isInstancePropertyWithoutInitializer(member)) { const propName = (member).name; if (isIdentifier(propName)) { const type = getTypeOfSymbol(getSymbolOfNode(member)); @@ -22647,6 +22666,12 @@ namespace ts { } } + function isInstancePropertyWithoutInitializer(node: Node) { + return node.kind === SyntaxKind.PropertyDeclaration && + !hasModifier(node, ModifierFlags.Static | ModifierFlags.Abstract) && + !(node).initializer; + } + function isPropertyInitializedInConstructor(propName: Identifier, propType: Type, constructor: ConstructorDeclaration) { const reference = createPropertyAccess(createThis(), propName); reference.flowNode = constructor.returnFlowNode; diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index c802018300..7c0336cf60 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1956,6 +1956,10 @@ "category": "Error", "code": 2564 }, + "Property '{0}' is used before being assigned.": { + "category": "Error", + "code": 2565 + }, "JSX element attributes type '{0}' may not be a union type.": { "category": "Error", "code": 2600