From f1762a04ea736565ae0d988cc7a39de9e9d53d4b Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 16 Nov 2017 10:35:14 -0800 Subject: [PATCH 01/18] Attach return control flow graph to contructor declaration nodes --- src/compiler/binder.ts | 7 ++++++- src/compiler/types.ts | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index a014674067..076abcba81 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -514,8 +514,9 @@ namespace ts { if (containerFlags & (ContainerFlags.IsFunctionExpression | ContainerFlags.IsObjectLiteralOrClassExpressionMethod)) { (currentFlow).container = node; } - currentReturnTarget = undefined; + currentReturnTarget = node.kind === SyntaxKind.Constructor ? createBranchLabel() : undefined; } + currentReturnTarget = isIIFE || node.kind === SyntaxKind.Constructor ? createBranchLabel() : undefined; currentBreakTarget = undefined; currentContinueTarget = undefined; activeLabels = undefined; @@ -535,6 +536,10 @@ namespace ts { currentFlow = finishFlowLabel(currentReturnTarget); } else { + if (node.kind === SyntaxKind.Constructor) { + addAntecedent(currentReturnTarget, currentFlow); + (node).returnFlowNode = currentFlow; + } currentFlow = saveCurrentFlow; } currentBreakTarget = saveBreakTarget; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 3141e3f22d..32d6dc72fb 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -929,6 +929,7 @@ namespace ts { kind: SyntaxKind.Constructor; parent?: ClassDeclaration | ClassExpression; body?: FunctionBody; + returnFlowNode?: FlowNode; } /** For when we encounter a semicolon in a class declaration. ES6 allows these as class elements. */ From 5f6d7f34d82a8fda2f5757b25705d96a17d1def9 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 16 Nov 2017 10:36:32 -0800 Subject: [PATCH 02/18] Add strict property initialization checks --- src/compiler/checker.ts | 30 +++++++++++++++++++++++++++- src/compiler/diagnosticMessages.json | 4 ++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 631960f24b..32182081c4 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -11925,7 +11925,7 @@ namespace ts { // on empty arrays are possible without implicit any errors and new element types can be inferred without // type mismatch errors. const resultType = getObjectFlags(evolvedType) & ObjectFlags.EvolvingArray && isEvolvingArrayOperationTarget(reference) ? anyArrayType : finalizeEvolvingArrayType(evolvedType); - if (reference.parent.kind === SyntaxKind.NonNullExpression && getTypeWithFacts(resultType, TypeFacts.NEUndefinedOrNull).flags & TypeFlags.Never) { + if (reference.parent && reference.parent.kind === SyntaxKind.NonNullExpression && getTypeWithFacts(resultType, TypeFacts.NEUndefinedOrNull).flags & TypeFlags.Never) { return declaredType; } return resultType; @@ -22072,6 +22072,7 @@ namespace ts { if (produceDiagnostics) { checkIndexConstraints(type); checkTypeForDuplicateIndexSignatures(node); + checkPropertyInitialization(node); } } @@ -22228,6 +22229,33 @@ namespace ts { return ok; } + function checkPropertyInitialization(node: ClassLikeDeclaration) { + if (!strictNullChecks || node.flags & NodeFlags.Ambient) { + return; + } + const constructor = findConstructorDeclaration(node); + for (const member of node.members) { + if (member.kind === SyntaxKind.PropertyDeclaration && !(member).initializer) { + const propName = (member).name; + if (isIdentifier(propName)) { + const type = getTypeOfSymbol(getSymbolOfNode(member)); + if (!(type.flags & TypeFlags.Any || getFalsyFlags(type) & TypeFlags.Undefined)) { + if (!constructor || !isPropertyInitializedInConstructor(propName, type, constructor)) { + error(member.name, Diagnostics.Property_0_has_no_initializer_and_is_not_definitely_assigned_in_the_constructor, declarationNameToString(propName)); + } + } + } + } + } + } + + function isPropertyInitializedInConstructor(propName: Identifier, propType: Type, constructor: ConstructorDeclaration) { + const reference = createPropertyAccess(createThis(), propName); + reference.flowNode = constructor.returnFlowNode; + const flowType = getFlowTypeOfReference(reference, propType, getOptionalType(propType)); + return !(getFalsyFlags(flowType) & TypeFlags.Undefined); + } + function checkInterfaceDeclaration(node: InterfaceDeclaration) { // Grammar checking if (!checkGrammarDecoratorsAndModifiers(node)) checkGrammarInterfaceDeclaration(node); diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index a552122609..df6accb443 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1928,6 +1928,10 @@ "category": "Error", "code": 2563 }, + "Property '{0}' has no initializer and is not definitely assigned in the constructor.": { + "category": "Error", + "code": 2564 + }, "JSX element attributes type '{0}' may not be a union type.": { "category": "Error", "code": 2600 From c11969f804bb27789211e61ed484a6e4e0508053 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 16 Nov 2017 10:57:15 -0800 Subject: [PATCH 03/18] Mark returnFlowNode property as internal --- src/compiler/types.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 32d6dc72fb..4f8351d791 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -929,7 +929,7 @@ namespace ts { kind: SyntaxKind.Constructor; parent?: ClassDeclaration | ClassExpression; body?: FunctionBody; - returnFlowNode?: FlowNode; + /* @internal */ returnFlowNode?: FlowNode; } /** For when we encounter a semicolon in a class declaration. ES6 allows these as class elements. */ @@ -3809,6 +3809,7 @@ namespace ts { strict?: boolean; strictFunctionTypes?: boolean; // Always combine with strict property strictNullChecks?: boolean; // Always combine with strict property + strictPropertyInitialization?: boolean; // Always combine with strict property /* @internal */ stripInternal?: boolean; suppressExcessPropertyErrors?: boolean; suppressImplicitAnyIndexErrors?: boolean; From 8cbff0159df1792e7d3bd87946b99a4eed042e4c Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 16 Nov 2017 10:58:00 -0800 Subject: [PATCH 04/18] Add --strictPropertyInitialization compiler option --- src/compiler/checker.ts | 3 ++- src/compiler/commandLineParser.ts | 7 +++++++ src/compiler/core.ts | 2 +- src/compiler/diagnosticMessages.json | 4 ++++ 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 32182081c4..b6af1c49b1 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -69,6 +69,7 @@ namespace ts { const allowSyntheticDefaultImports = getAllowSyntheticDefaultImports(compilerOptions); const strictNullChecks = getStrictOptionValue(compilerOptions, "strictNullChecks"); const strictFunctionTypes = getStrictOptionValue(compilerOptions, "strictFunctionTypes"); + const strictPropertyInitialization = getStrictOptionValue(compilerOptions, "strictPropertyInitialization"); const noImplicitAny = getStrictOptionValue(compilerOptions, "noImplicitAny"); const noImplicitThis = getStrictOptionValue(compilerOptions, "noImplicitThis"); @@ -22230,7 +22231,7 @@ namespace ts { } function checkPropertyInitialization(node: ClassLikeDeclaration) { - if (!strictNullChecks || node.flags & NodeFlags.Ambient) { + if (!strictNullChecks || !strictPropertyInitialization || node.flags & NodeFlags.Ambient) { return; } const constructor = findConstructorDeclaration(node); diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 8cec21113c..989bb6a714 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -277,6 +277,13 @@ namespace ts { category: Diagnostics.Strict_Type_Checking_Options, description: Diagnostics.Enable_strict_checking_of_function_types }, + { + name: "strictPropertyInitialization", + type: "boolean", + showInSimplifiedHelpView: true, + category: Diagnostics.Strict_Type_Checking_Options, + description: Diagnostics.Enable_strict_checking_of_property_initialization_in_classes + }, { name: "noImplicitThis", type: "boolean", diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 9f839923e7..aa91b6d9e9 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1923,7 +1923,7 @@ namespace ts { : moduleKind === ModuleKind.System; } - export type StrictOptionName = "noImplicitAny" | "noImplicitThis" | "strictNullChecks" | "strictFunctionTypes" | "alwaysStrict"; + export type StrictOptionName = "noImplicitAny" | "noImplicitThis" | "strictNullChecks" | "strictFunctionTypes" | "strictPropertyInitialization" | "alwaysStrict"; export function getStrictOptionValue(compilerOptions: CompilerOptions, flag: StrictOptionName): boolean { return compilerOptions[flag] === undefined ? compilerOptions.strict : compilerOptions[flag]; diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index df6accb443..5adcc981a2 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -3330,6 +3330,10 @@ "category": "Message", "code": 6186 }, + "Enable strict checking of property initialization in classes.": { + "category": "Message", + "code": 6187 + }, "Variable '{0}' implicitly has an '{1}' type.": { "category": "Error", "code": 7005 From 3357aae2d871a7486dfaa59395f59f2bdfe80409 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 16 Nov 2017 10:58:12 -0800 Subject: [PATCH 05/18] Update tests --- tests/cases/compiler/indexedAccessTypeConstraints.ts | 2 +- tests/cases/compiler/typeVariableTypeGuards.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/cases/compiler/indexedAccessTypeConstraints.ts b/tests/cases/compiler/indexedAccessTypeConstraints.ts index c1c1bca198..aee8b6bcb6 100644 --- a/tests/cases/compiler/indexedAccessTypeConstraints.ts +++ b/tests/cases/compiler/indexedAccessTypeConstraints.ts @@ -11,7 +11,7 @@ type Data = { }; class Parent { - private data: Data; + constructor(private data: Data) {} getData(): Data { return this.data; } diff --git a/tests/cases/compiler/typeVariableTypeGuards.ts b/tests/cases/compiler/typeVariableTypeGuards.ts index 0a4221c93a..eb15b8689b 100644 --- a/tests/cases/compiler/typeVariableTypeGuards.ts +++ b/tests/cases/compiler/typeVariableTypeGuards.ts @@ -7,7 +7,7 @@ interface Foo { } class A

> { - props: Readonly

+ constructor(public props: Readonly

) {} doSomething() { this.props.foo && this.props.foo() } From 3a7a99f7c51da152563658659032cc32e8e13881 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 16 Nov 2017 11:08:03 -0800 Subject: [PATCH 06/18] Accept new baselines --- .../reference/api/tsserverlibrary.d.ts | 1 + tests/baselines/reference/api/typescript.d.ts | 1 + .../reference/indexedAccessTypeConstraints.js | 5 +-- .../indexedAccessTypeConstraints.symbols | 18 +++++----- .../indexedAccessTypeConstraints.types | 2 +- .../tsconfig.json | 1 + .../tsconfig.json | 1 + .../tsconfig.json | 1 + .../tsconfig.json | 1 + .../tsconfig.json | 1 + .../tsconfig.json | 1 + .../tsconfig.json | 1 + .../tsconfig.json | 1 + .../reference/typeVariableTypeGuards.js | 10 +++--- .../reference/typeVariableTypeGuards.symbols | 36 +++++++++---------- .../reference/typeVariableTypeGuards.types | 4 +-- .../cases/compiler/typeVariableTypeGuards.ts | 2 +- 17 files changed, 50 insertions(+), 37 deletions(-) diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 096ad71c89..a7561ef6b8 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -2279,6 +2279,7 @@ declare namespace ts { strict?: boolean; strictFunctionTypes?: boolean; strictNullChecks?: boolean; + strictPropertyInitialization?: boolean; suppressExcessPropertyErrors?: boolean; suppressImplicitAnyIndexErrors?: boolean; target?: ScriptTarget; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index dcc077d2cd..4f15bfb0f7 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -2279,6 +2279,7 @@ declare namespace ts { strict?: boolean; strictFunctionTypes?: boolean; strictNullChecks?: boolean; + strictPropertyInitialization?: boolean; suppressExcessPropertyErrors?: boolean; suppressImplicitAnyIndexErrors?: boolean; target?: ScriptTarget; diff --git a/tests/baselines/reference/indexedAccessTypeConstraints.js b/tests/baselines/reference/indexedAccessTypeConstraints.js index 236f2f33ff..7a218586d8 100644 --- a/tests/baselines/reference/indexedAccessTypeConstraints.js +++ b/tests/baselines/reference/indexedAccessTypeConstraints.js @@ -10,7 +10,7 @@ type Data = { }; class Parent { - private data: Data; + constructor(private data: Data) {} getData(): Data { return this.data; } @@ -50,7 +50,8 @@ var __extends = (this && this.__extends) || (function () { })(); exports.__esModule = true; var Parent = /** @class */ (function () { - function Parent() { + function Parent(data) { + this.data = data; } Parent.prototype.getData = function () { return this.data; diff --git a/tests/baselines/reference/indexedAccessTypeConstraints.symbols b/tests/baselines/reference/indexedAccessTypeConstraints.symbols index 6d81534aa2..4937db57f3 100644 --- a/tests/baselines/reference/indexedAccessTypeConstraints.symbols +++ b/tests/baselines/reference/indexedAccessTypeConstraints.symbols @@ -29,20 +29,20 @@ class Parent { >Parent : Symbol(Parent, Decl(indexedAccessTypeConstraints.ts, 8, 2)) >M : Symbol(M, Decl(indexedAccessTypeConstraints.ts, 10, 13)) - private data: Data; ->data : Symbol(Parent.data, Decl(indexedAccessTypeConstraints.ts, 10, 17)) + constructor(private data: Data) {} +>data : Symbol(Parent.data, Decl(indexedAccessTypeConstraints.ts, 11, 16)) >Data : Symbol(Data, Decl(indexedAccessTypeConstraints.ts, 4, 1)) >M : Symbol(M, Decl(indexedAccessTypeConstraints.ts, 10, 13)) getData(): Data { ->getData : Symbol(Parent.getData, Decl(indexedAccessTypeConstraints.ts, 11, 26)) +>getData : Symbol(Parent.getData, Decl(indexedAccessTypeConstraints.ts, 11, 41)) >Data : Symbol(Data, Decl(indexedAccessTypeConstraints.ts, 4, 1)) >M : Symbol(M, Decl(indexedAccessTypeConstraints.ts, 10, 13)) return this.data; ->this.data : Symbol(Parent.data, Decl(indexedAccessTypeConstraints.ts, 10, 17)) +>this.data : Symbol(Parent.data, Decl(indexedAccessTypeConstraints.ts, 11, 16)) >this : Symbol(Parent, Decl(indexedAccessTypeConstraints.ts, 8, 2)) ->data : Symbol(Parent.data, Decl(indexedAccessTypeConstraints.ts, 10, 17)) +>data : Symbol(Parent.data, Decl(indexedAccessTypeConstraints.ts, 11, 16)) } } @@ -59,9 +59,9 @@ export class Foo extends Parent> { return this.getData().get('content'); >this.getData().get : Symbol(get, Decl(indexedAccessTypeConstraints.ts, 6, 16)) ->this.getData : Symbol(Parent.getData, Decl(indexedAccessTypeConstraints.ts, 11, 26)) +>this.getData : Symbol(Parent.getData, Decl(indexedAccessTypeConstraints.ts, 11, 41)) >this : Symbol(Foo, Decl(indexedAccessTypeConstraints.ts, 15, 1)) ->getData : Symbol(Parent.getData, Decl(indexedAccessTypeConstraints.ts, 11, 26)) +>getData : Symbol(Parent.getData, Decl(indexedAccessTypeConstraints.ts, 11, 41)) >get : Symbol(get, Decl(indexedAccessTypeConstraints.ts, 6, 16)) } } @@ -81,9 +81,9 @@ export class Bar> extends Parent { return this.getData().get('content'); >this.getData().get : Symbol(get, Decl(indexedAccessTypeConstraints.ts, 6, 16)) ->this.getData : Symbol(Parent.getData, Decl(indexedAccessTypeConstraints.ts, 11, 26)) +>this.getData : Symbol(Parent.getData, Decl(indexedAccessTypeConstraints.ts, 11, 41)) >this : Symbol(Bar, Decl(indexedAccessTypeConstraints.ts, 21, 1)) ->getData : Symbol(Parent.getData, Decl(indexedAccessTypeConstraints.ts, 11, 26)) +>getData : Symbol(Parent.getData, Decl(indexedAccessTypeConstraints.ts, 11, 41)) >get : Symbol(get, Decl(indexedAccessTypeConstraints.ts, 6, 16)) } } diff --git a/tests/baselines/reference/indexedAccessTypeConstraints.types b/tests/baselines/reference/indexedAccessTypeConstraints.types index c7373fe5d4..dad5fd7d61 100644 --- a/tests/baselines/reference/indexedAccessTypeConstraints.types +++ b/tests/baselines/reference/indexedAccessTypeConstraints.types @@ -29,7 +29,7 @@ class Parent { >Parent : Parent >M : M - private data: Data; + constructor(private data: Data) {} >data : { get: (prop: K) => M[K]; } >Data : { get: (prop: K) => T[K]; } >M : M diff --git a/tests/baselines/reference/tsConfig/Default initialized TSConfig/tsconfig.json b/tests/baselines/reference/tsConfig/Default initialized TSConfig/tsconfig.json index 08887fc6c9..25a2c11d33 100644 --- a/tests/baselines/reference/tsConfig/Default initialized TSConfig/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Default initialized TSConfig/tsconfig.json @@ -23,6 +23,7 @@ // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with boolean value compiler options/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with boolean value compiler options/tsconfig.json index ca2b4aa408..d6534c834a 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with boolean value compiler options/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with boolean value compiler options/tsconfig.json @@ -23,6 +23,7 @@ // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with enum value compiler options/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with enum value compiler options/tsconfig.json index 9437685c29..414f3b5790 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with enum value compiler options/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with enum value compiler options/tsconfig.json @@ -23,6 +23,7 @@ // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with files options/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with files options/tsconfig.json index d2e7e85ad5..940970d297 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with files options/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with files options/tsconfig.json @@ -23,6 +23,7 @@ // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option value/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option value/tsconfig.json index 3f4100033d..074f24cad5 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option value/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option value/tsconfig.json @@ -23,6 +23,7 @@ // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option/tsconfig.json index 08887fc6c9..25a2c11d33 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option/tsconfig.json @@ -23,6 +23,7 @@ // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options with enum value/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options with enum value/tsconfig.json index 22cb044420..a5eae1a465 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options with enum value/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options with enum value/tsconfig.json @@ -23,6 +23,7 @@ // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options/tsconfig.json index fc3321600f..2468ac7921 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options/tsconfig.json @@ -23,6 +23,7 @@ // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ diff --git a/tests/baselines/reference/typeVariableTypeGuards.js b/tests/baselines/reference/typeVariableTypeGuards.js index 378146359c..379d9a331d 100644 --- a/tests/baselines/reference/typeVariableTypeGuards.js +++ b/tests/baselines/reference/typeVariableTypeGuards.js @@ -6,7 +6,7 @@ interface Foo { } class A

> { - props: Readonly

+ constructor(public props: Readonly

) {} doSomething() { this.props.foo && this.props.foo() } @@ -19,7 +19,7 @@ interface Banana { } class Monkey { - a: T; + constructor(public a: T) {} render() { if (this.a) { this.a.color; @@ -96,7 +96,8 @@ var __extends = (this && this.__extends) || (function () { }; })(); var A = /** @class */ (function () { - function A() { + function A(props) { + this.props = props; } A.prototype.doSomething = function () { this.props.foo && this.props.foo(); @@ -104,7 +105,8 @@ var A = /** @class */ (function () { return A; }()); var Monkey = /** @class */ (function () { - function Monkey() { + function Monkey(a) { + this.a = a; } Monkey.prototype.render = function () { if (this.a) { diff --git a/tests/baselines/reference/typeVariableTypeGuards.symbols b/tests/baselines/reference/typeVariableTypeGuards.symbols index d201d0538a..5a91e712e1 100644 --- a/tests/baselines/reference/typeVariableTypeGuards.symbols +++ b/tests/baselines/reference/typeVariableTypeGuards.symbols @@ -14,24 +14,24 @@ class A

> { >Partial : Symbol(Partial, Decl(lib.d.ts, --, --)) >Foo : Symbol(Foo, Decl(typeVariableTypeGuards.ts, 0, 0)) - props: Readonly

->props : Symbol(A.props, Decl(typeVariableTypeGuards.ts, 6, 33)) + constructor(public props: Readonly

) {} +>props : Symbol(A.props, Decl(typeVariableTypeGuards.ts, 7, 16)) >Readonly : Symbol(Readonly, Decl(lib.d.ts, --, --)) >P : Symbol(P, Decl(typeVariableTypeGuards.ts, 6, 8)) doSomething() { ->doSomething : Symbol(A.doSomething, Decl(typeVariableTypeGuards.ts, 7, 22)) +>doSomething : Symbol(A.doSomething, Decl(typeVariableTypeGuards.ts, 7, 45)) this.props.foo && this.props.foo() >this.props.foo : Symbol(foo, Decl(typeVariableTypeGuards.ts, 2, 15)) ->this.props : Symbol(A.props, Decl(typeVariableTypeGuards.ts, 6, 33)) +>this.props : Symbol(A.props, Decl(typeVariableTypeGuards.ts, 7, 16)) >this : Symbol(A, Decl(typeVariableTypeGuards.ts, 4, 1)) ->props : Symbol(A.props, Decl(typeVariableTypeGuards.ts, 6, 33)) +>props : Symbol(A.props, Decl(typeVariableTypeGuards.ts, 7, 16)) >foo : Symbol(foo, Decl(typeVariableTypeGuards.ts, 2, 15)) >this.props.foo : Symbol(foo, Decl(typeVariableTypeGuards.ts, 2, 15)) ->this.props : Symbol(A.props, Decl(typeVariableTypeGuards.ts, 6, 33)) +>this.props : Symbol(A.props, Decl(typeVariableTypeGuards.ts, 7, 16)) >this : Symbol(A, Decl(typeVariableTypeGuards.ts, 4, 1)) ->props : Symbol(A.props, Decl(typeVariableTypeGuards.ts, 6, 33)) +>props : Symbol(A.props, Decl(typeVariableTypeGuards.ts, 7, 16)) >foo : Symbol(foo, Decl(typeVariableTypeGuards.ts, 2, 15)) } } @@ -50,23 +50,23 @@ class Monkey { >T : Symbol(T, Decl(typeVariableTypeGuards.ts, 19, 13)) >Banana : Symbol(Banana, Decl(typeVariableTypeGuards.ts, 11, 1)) - a: T; ->a : Symbol(Monkey.a, Decl(typeVariableTypeGuards.ts, 19, 44)) + constructor(public a: T) {} +>a : Symbol(Monkey.a, Decl(typeVariableTypeGuards.ts, 20, 16)) >T : Symbol(T, Decl(typeVariableTypeGuards.ts, 19, 13)) render() { ->render : Symbol(Monkey.render, Decl(typeVariableTypeGuards.ts, 20, 9)) +>render : Symbol(Monkey.render, Decl(typeVariableTypeGuards.ts, 20, 31)) if (this.a) { ->this.a : Symbol(Monkey.a, Decl(typeVariableTypeGuards.ts, 19, 44)) +>this.a : Symbol(Monkey.a, Decl(typeVariableTypeGuards.ts, 20, 16)) >this : Symbol(Monkey, Decl(typeVariableTypeGuards.ts, 17, 1)) ->a : Symbol(Monkey.a, Decl(typeVariableTypeGuards.ts, 19, 44)) +>a : Symbol(Monkey.a, Decl(typeVariableTypeGuards.ts, 20, 16)) this.a.color; >this.a.color : Symbol(Banana.color, Decl(typeVariableTypeGuards.ts, 15, 18)) ->this.a : Symbol(Monkey.a, Decl(typeVariableTypeGuards.ts, 19, 44)) +>this.a : Symbol(Monkey.a, Decl(typeVariableTypeGuards.ts, 20, 16)) >this : Symbol(Monkey, Decl(typeVariableTypeGuards.ts, 17, 1)) ->a : Symbol(Monkey.a, Decl(typeVariableTypeGuards.ts, 19, 44)) +>a : Symbol(Monkey.a, Decl(typeVariableTypeGuards.ts, 20, 16)) >color : Symbol(Banana.color, Decl(typeVariableTypeGuards.ts, 15, 18)) } } @@ -86,15 +86,15 @@ class BigMonkey extends Monkey { >render : Symbol(BigMonkey.render, Decl(typeVariableTypeGuards.ts, 31, 43)) if (this.a) { ->this.a : Symbol(Monkey.a, Decl(typeVariableTypeGuards.ts, 19, 44)) +>this.a : Symbol(Monkey.a, Decl(typeVariableTypeGuards.ts, 20, 16)) >this : Symbol(BigMonkey, Decl(typeVariableTypeGuards.ts, 29, 1)) ->a : Symbol(Monkey.a, Decl(typeVariableTypeGuards.ts, 19, 44)) +>a : Symbol(Monkey.a, Decl(typeVariableTypeGuards.ts, 20, 16)) this.a.color; >this.a.color : Symbol(Banana.color, Decl(typeVariableTypeGuards.ts, 15, 18)) ->this.a : Symbol(Monkey.a, Decl(typeVariableTypeGuards.ts, 19, 44)) +>this.a : Symbol(Monkey.a, Decl(typeVariableTypeGuards.ts, 20, 16)) >this : Symbol(BigMonkey, Decl(typeVariableTypeGuards.ts, 29, 1)) ->a : Symbol(Monkey.a, Decl(typeVariableTypeGuards.ts, 19, 44)) +>a : Symbol(Monkey.a, Decl(typeVariableTypeGuards.ts, 20, 16)) >color : Symbol(Banana.color, Decl(typeVariableTypeGuards.ts, 15, 18)) } } diff --git a/tests/baselines/reference/typeVariableTypeGuards.types b/tests/baselines/reference/typeVariableTypeGuards.types index bfcf851648..c3fc637b5b 100644 --- a/tests/baselines/reference/typeVariableTypeGuards.types +++ b/tests/baselines/reference/typeVariableTypeGuards.types @@ -14,7 +14,7 @@ class A

> { >Partial : Partial >Foo : Foo - props: Readonly

+ constructor(public props: Readonly

) {} >props : Readonly

>Readonly : Readonly >P : P @@ -52,7 +52,7 @@ class Monkey { >T : T >Banana : Banana - a: T; + constructor(public a: T) {} >a : T >T : T diff --git a/tests/cases/compiler/typeVariableTypeGuards.ts b/tests/cases/compiler/typeVariableTypeGuards.ts index eb15b8689b..91e5e7a552 100644 --- a/tests/cases/compiler/typeVariableTypeGuards.ts +++ b/tests/cases/compiler/typeVariableTypeGuards.ts @@ -20,7 +20,7 @@ interface Banana { } class Monkey { - a: T; + constructor(public a: T) {} render() { if (this.a) { this.a.color; From 190f99e460bda74ccc0ca4add1193b831f07e434 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 16 Nov 2017 17:30:14 -0800 Subject: [PATCH 07/18] Exclude static properties --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b6af1c49b1..5d80874007 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -22236,7 +22236,7 @@ namespace ts { } const constructor = findConstructorDeclaration(node); for (const member of node.members) { - if (member.kind === SyntaxKind.PropertyDeclaration && !(member).initializer) { + if (member.kind === SyntaxKind.PropertyDeclaration && !hasModifier(member, ModifierFlags.Static) && !(member).initializer) { const propName = (member).name; if (isIdentifier(propName)) { const type = getTypeOfSymbol(getSymbolOfNode(member)); From 041d04577ef760bb3331513a3be711bbb88164e2 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 17 Nov 2017 09:05:46 -0800 Subject: [PATCH 08/18] Revise return control flow graph construction logic --- src/compiler/binder.ts | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 076abcba81..df794c5e70 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -506,16 +506,14 @@ namespace ts { const isIIFE = containerFlags & ContainerFlags.IsFunctionExpression && !hasModifier(node, ModifierFlags.Async) && !!getImmediatelyInvokedFunctionExpression(node); // A non-async IIFE is considered part of the containing control flow. Return statements behave // similarly to break statements that exit to a label just past the statement body. - if (isIIFE) { - currentReturnTarget = createBranchLabel(); - } - else { + if (!isIIFE) { currentFlow = { flags: FlowFlags.Start }; if (containerFlags & (ContainerFlags.IsFunctionExpression | ContainerFlags.IsObjectLiteralOrClassExpressionMethod)) { (currentFlow).container = node; } - currentReturnTarget = node.kind === SyntaxKind.Constructor ? createBranchLabel() : undefined; } + // We create a return control flow graph for IIFEs and constructors. For constructors + // we use the return control flow graph in strict property intialization checks. currentReturnTarget = isIIFE || node.kind === SyntaxKind.Constructor ? createBranchLabel() : undefined; currentBreakTarget = undefined; currentContinueTarget = undefined; @@ -531,15 +529,14 @@ namespace ts { if (node.kind === SyntaxKind.SourceFile) { node.flags |= emitFlags; } - if (isIIFE) { + if (currentReturnTarget) { addAntecedent(currentReturnTarget, currentFlow); currentFlow = finishFlowLabel(currentReturnTarget); - } - else { if (node.kind === SyntaxKind.Constructor) { - addAntecedent(currentReturnTarget, currentFlow); (node).returnFlowNode = currentFlow; } + } + if (!isIIFE) { currentFlow = saveCurrentFlow; } currentBreakTarget = saveBreakTarget; From 4141a37ba7f00b4e829af4c9dc0e1b5a5913aa47 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 17 Nov 2017 09:52:37 -0800 Subject: [PATCH 09/18] Add tests --- .../strictPropertyInitialization.ts | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 tests/cases/conformance/classes/propertyMemberDeclarations/strictPropertyInitialization.ts diff --git a/tests/cases/conformance/classes/propertyMemberDeclarations/strictPropertyInitialization.ts b/tests/cases/conformance/classes/propertyMemberDeclarations/strictPropertyInitialization.ts new file mode 100644 index 0000000000..326e4542d2 --- /dev/null +++ b/tests/cases/conformance/classes/propertyMemberDeclarations/strictPropertyInitialization.ts @@ -0,0 +1,77 @@ +// @strict: true +// @declaration: true + +// Properties with non-undefined types require initialization + +class C1 { + a: number; // Error + b: number | undefined; + c: number | null; // Error + d?: number; +} + +// No strict initialization checks in ambient contexts + +declare class C2 { + a: number; + b: number | undefined; + c: number | null; + d?: number; +} + +// No strict initialization checks for static members + +class C3 { + static a: number; + static b: number | undefined; + static c: number | null; + static d?: number; +} + +// Initializer satisfies strict initialization check + +class C4 { + a = 0; + b: number = 0; + c: string = "abc"; +} + +// Assignment in constructor satisfies strict initialization check + +class C5 { + a: number; + constructor() { + this.a = 0; + } +} + +// All code paths must contain assignment + +class C6 { + a: number; // Error + constructor(cond: boolean) { + if (cond) { + return; + } + this.a = 0; + } +} + +class C7 { + a: number; + constructor(cond: boolean) { + if (cond) { + this.a = 1; + return; + } + this.a = 0; + } +} + +// Properties with string literal names aren't checked + +class C8 { + a: number; // Error + "b": number; + 0: number; +} From 49d6ddf1022a908e684f72076d4cdb8605e761d4 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 17 Nov 2017 09:52:46 -0800 Subject: [PATCH 10/18] Accept new baselines --- .../strictPropertyInitialization.errors.txt | 90 +++++++++ .../reference/strictPropertyInitialization.js | 176 ++++++++++++++++++ .../strictPropertyInitialization.symbols | 147 +++++++++++++++ .../strictPropertyInitialization.types | 161 ++++++++++++++++ 4 files changed, 574 insertions(+) create mode 100644 tests/baselines/reference/strictPropertyInitialization.errors.txt create mode 100644 tests/baselines/reference/strictPropertyInitialization.js create mode 100644 tests/baselines/reference/strictPropertyInitialization.symbols create mode 100644 tests/baselines/reference/strictPropertyInitialization.types diff --git a/tests/baselines/reference/strictPropertyInitialization.errors.txt b/tests/baselines/reference/strictPropertyInitialization.errors.txt new file mode 100644 index 0000000000..6d13ea3646 --- /dev/null +++ b/tests/baselines/reference/strictPropertyInitialization.errors.txt @@ -0,0 +1,90 @@ +tests/cases/conformance/classes/propertyMemberDeclarations/strictPropertyInitialization.ts(4,5): error TS2564: Property 'a' has no initializer and is not definitely assigned in the constructor. +tests/cases/conformance/classes/propertyMemberDeclarations/strictPropertyInitialization.ts(6,5): error TS2564: Property 'c' has no initializer and is not definitely assigned in the constructor. +tests/cases/conformance/classes/propertyMemberDeclarations/strictPropertyInitialization.ts(48,5): error TS2564: Property 'a' has no initializer and is not definitely assigned in the constructor. +tests/cases/conformance/classes/propertyMemberDeclarations/strictPropertyInitialization.ts(71,5): error TS2564: Property 'a' has no initializer and is not definitely assigned in the constructor. + + +==== tests/cases/conformance/classes/propertyMemberDeclarations/strictPropertyInitialization.ts (4 errors) ==== + // Properties with non-undefined types require initialization + + class C1 { + a: number; // Error + ~ +!!! error TS2564: Property 'a' has no initializer and is not definitely assigned in the constructor. + b: number | undefined; + c: number | null; // Error + ~ +!!! error TS2564: Property 'c' has no initializer and is not definitely assigned in the constructor. + d?: number; + } + + // No strict initialization checks in ambient contexts + + declare class C2 { + a: number; + b: number | undefined; + c: number | null; + d?: number; + } + + // No strict initialization checks for static members + + class C3 { + static a: number; + static b: number | undefined; + static c: number | null; + static d?: number; + } + + // Initializer satisfies strict initialization check + + class C4 { + a = 0; + b: number = 0; + c: string = "abc"; + } + + // Assignment in constructor satisfies strict initialization check + + class C5 { + a: number; + constructor() { + this.a = 0; + } + } + + // All code paths must contain assignment + + class C6 { + a: number; // Error + ~ +!!! error TS2564: Property 'a' has no initializer and is not definitely assigned in the constructor. + constructor(cond: boolean) { + if (cond) { + return; + } + this.a = 0; + } + } + + class C7 { + a: number; + constructor(cond: boolean) { + if (cond) { + this.a = 1; + return; + } + this.a = 0; + } + } + + // Properties with string literal names aren't checked + + class C8 { + a: number; // Error + ~ +!!! error TS2564: Property 'a' has no initializer and is not definitely assigned in the constructor. + "b": number; + 0: number; + } + \ No newline at end of file diff --git a/tests/baselines/reference/strictPropertyInitialization.js b/tests/baselines/reference/strictPropertyInitialization.js new file mode 100644 index 0000000000..7379e5a157 --- /dev/null +++ b/tests/baselines/reference/strictPropertyInitialization.js @@ -0,0 +1,176 @@ +//// [strictPropertyInitialization.ts] +// Properties with non-undefined types require initialization + +class C1 { + a: number; // Error + b: number | undefined; + c: number | null; // Error + d?: number; +} + +// No strict initialization checks in ambient contexts + +declare class C2 { + a: number; + b: number | undefined; + c: number | null; + d?: number; +} + +// No strict initialization checks for static members + +class C3 { + static a: number; + static b: number | undefined; + static c: number | null; + static d?: number; +} + +// Initializer satisfies strict initialization check + +class C4 { + a = 0; + b: number = 0; + c: string = "abc"; +} + +// Assignment in constructor satisfies strict initialization check + +class C5 { + a: number; + constructor() { + this.a = 0; + } +} + +// All code paths must contain assignment + +class C6 { + a: number; // Error + constructor(cond: boolean) { + if (cond) { + return; + } + this.a = 0; + } +} + +class C7 { + a: number; + constructor(cond: boolean) { + if (cond) { + this.a = 1; + return; + } + this.a = 0; + } +} + +// Properties with string literal names aren't checked + +class C8 { + a: number; // Error + "b": number; + 0: number; +} + + +//// [strictPropertyInitialization.js] +"use strict"; +// Properties with non-undefined types require initialization +var C1 = /** @class */ (function () { + function C1() { + } + return C1; +}()); +// No strict initialization checks for static members +var C3 = /** @class */ (function () { + function C3() { + } + return C3; +}()); +// Initializer satisfies strict initialization check +var C4 = /** @class */ (function () { + function C4() { + this.a = 0; + this.b = 0; + this.c = "abc"; + } + return C4; +}()); +// Assignment in constructor satisfies strict initialization check +var C5 = /** @class */ (function () { + function C5() { + this.a = 0; + } + return C5; +}()); +// All code paths must contain assignment +var C6 = /** @class */ (function () { + function C6(cond) { + if (cond) { + return; + } + this.a = 0; + } + return C6; +}()); +var C7 = /** @class */ (function () { + function C7(cond) { + if (cond) { + this.a = 1; + return; + } + this.a = 0; + } + return C7; +}()); +// Properties with string literal names aren't checked +var C8 = /** @class */ (function () { + function C8() { + } + return C8; +}()); + + +//// [strictPropertyInitialization.d.ts] +declare class C1 { + a: number; + b: number | undefined; + c: number | null; + d?: number; +} +declare class C2 { + a: number; + b: number | undefined; + c: number | null; + d?: number; +} +declare class C3 { + static a: number; + static b: number | undefined; + static c: number | null; + static d?: number; +} +declare class C4 { + a: number; + b: number; + c: string; +} +declare class C5 { + a: number; + constructor(); +} +declare class C6 { + a: number; + constructor(cond: boolean); +} +declare class C7 { + a: number; + constructor(cond: boolean); +} +declare class C8 { + a: number; + "b": number; + 0: number; +} diff --git a/tests/baselines/reference/strictPropertyInitialization.symbols b/tests/baselines/reference/strictPropertyInitialization.symbols new file mode 100644 index 0000000000..54b365732c --- /dev/null +++ b/tests/baselines/reference/strictPropertyInitialization.symbols @@ -0,0 +1,147 @@ +=== tests/cases/conformance/classes/propertyMemberDeclarations/strictPropertyInitialization.ts === +// Properties with non-undefined types require initialization + +class C1 { +>C1 : Symbol(C1, Decl(strictPropertyInitialization.ts, 0, 0)) + + a: number; // Error +>a : Symbol(C1.a, Decl(strictPropertyInitialization.ts, 2, 10)) + + b: number | undefined; +>b : Symbol(C1.b, Decl(strictPropertyInitialization.ts, 3, 14)) + + c: number | null; // Error +>c : Symbol(C1.c, Decl(strictPropertyInitialization.ts, 4, 26)) + + d?: number; +>d : Symbol(C1.d, Decl(strictPropertyInitialization.ts, 5, 21)) +} + +// No strict initialization checks in ambient contexts + +declare class C2 { +>C2 : Symbol(C2, Decl(strictPropertyInitialization.ts, 7, 1)) + + a: number; +>a : Symbol(C2.a, Decl(strictPropertyInitialization.ts, 11, 18)) + + b: number | undefined; +>b : Symbol(C2.b, Decl(strictPropertyInitialization.ts, 12, 14)) + + c: number | null; +>c : Symbol(C2.c, Decl(strictPropertyInitialization.ts, 13, 26)) + + d?: number; +>d : Symbol(C2.d, Decl(strictPropertyInitialization.ts, 14, 21)) +} + +// No strict initialization checks for static members + +class C3 { +>C3 : Symbol(C3, Decl(strictPropertyInitialization.ts, 16, 1)) + + static a: number; +>a : Symbol(C3.a, Decl(strictPropertyInitialization.ts, 20, 10)) + + static b: number | undefined; +>b : Symbol(C3.b, Decl(strictPropertyInitialization.ts, 21, 21)) + + static c: number | null; +>c : Symbol(C3.c, Decl(strictPropertyInitialization.ts, 22, 33)) + + static d?: number; +>d : Symbol(C3.d, Decl(strictPropertyInitialization.ts, 23, 28)) +} + +// Initializer satisfies strict initialization check + +class C4 { +>C4 : Symbol(C4, Decl(strictPropertyInitialization.ts, 25, 1)) + + a = 0; +>a : Symbol(C4.a, Decl(strictPropertyInitialization.ts, 29, 10)) + + b: number = 0; +>b : Symbol(C4.b, Decl(strictPropertyInitialization.ts, 30, 10)) + + c: string = "abc"; +>c : Symbol(C4.c, Decl(strictPropertyInitialization.ts, 31, 18)) +} + +// Assignment in constructor satisfies strict initialization check + +class C5 { +>C5 : Symbol(C5, Decl(strictPropertyInitialization.ts, 33, 1)) + + a: number; +>a : Symbol(C5.a, Decl(strictPropertyInitialization.ts, 37, 10)) + + constructor() { + this.a = 0; +>this.a : Symbol(C5.a, Decl(strictPropertyInitialization.ts, 37, 10)) +>this : Symbol(C5, Decl(strictPropertyInitialization.ts, 33, 1)) +>a : Symbol(C5.a, Decl(strictPropertyInitialization.ts, 37, 10)) + } +} + +// All code paths must contain assignment + +class C6 { +>C6 : Symbol(C6, Decl(strictPropertyInitialization.ts, 42, 1)) + + a: number; // Error +>a : Symbol(C6.a, Decl(strictPropertyInitialization.ts, 46, 10)) + + constructor(cond: boolean) { +>cond : Symbol(cond, Decl(strictPropertyInitialization.ts, 48, 16)) + + if (cond) { +>cond : Symbol(cond, Decl(strictPropertyInitialization.ts, 48, 16)) + + return; + } + this.a = 0; +>this.a : Symbol(C6.a, Decl(strictPropertyInitialization.ts, 46, 10)) +>this : Symbol(C6, Decl(strictPropertyInitialization.ts, 42, 1)) +>a : Symbol(C6.a, Decl(strictPropertyInitialization.ts, 46, 10)) + } +} + +class C7 { +>C7 : Symbol(C7, Decl(strictPropertyInitialization.ts, 54, 1)) + + a: number; +>a : Symbol(C7.a, Decl(strictPropertyInitialization.ts, 56, 10)) + + constructor(cond: boolean) { +>cond : Symbol(cond, Decl(strictPropertyInitialization.ts, 58, 16)) + + if (cond) { +>cond : Symbol(cond, Decl(strictPropertyInitialization.ts, 58, 16)) + + this.a = 1; +>this.a : Symbol(C7.a, Decl(strictPropertyInitialization.ts, 56, 10)) +>this : Symbol(C7, Decl(strictPropertyInitialization.ts, 54, 1)) +>a : Symbol(C7.a, Decl(strictPropertyInitialization.ts, 56, 10)) + + return; + } + this.a = 0; +>this.a : Symbol(C7.a, Decl(strictPropertyInitialization.ts, 56, 10)) +>this : Symbol(C7, Decl(strictPropertyInitialization.ts, 54, 1)) +>a : Symbol(C7.a, Decl(strictPropertyInitialization.ts, 56, 10)) + } +} + +// Properties with string literal names aren't checked + +class C8 { +>C8 : Symbol(C8, Decl(strictPropertyInitialization.ts, 65, 1)) + + a: number; // Error +>a : Symbol(C8.a, Decl(strictPropertyInitialization.ts, 69, 10)) + + "b": number; + 0: number; +} + diff --git a/tests/baselines/reference/strictPropertyInitialization.types b/tests/baselines/reference/strictPropertyInitialization.types new file mode 100644 index 0000000000..6c1e35908a --- /dev/null +++ b/tests/baselines/reference/strictPropertyInitialization.types @@ -0,0 +1,161 @@ +=== tests/cases/conformance/classes/propertyMemberDeclarations/strictPropertyInitialization.ts === +// Properties with non-undefined types require initialization + +class C1 { +>C1 : C1 + + a: number; // Error +>a : number + + b: number | undefined; +>b : number | undefined + + c: number | null; // Error +>c : number | null +>null : null + + d?: number; +>d : number | undefined +} + +// No strict initialization checks in ambient contexts + +declare class C2 { +>C2 : C2 + + a: number; +>a : number + + b: number | undefined; +>b : number | undefined + + c: number | null; +>c : number | null +>null : null + + d?: number; +>d : number | undefined +} + +// No strict initialization checks for static members + +class C3 { +>C3 : C3 + + static a: number; +>a : number + + static b: number | undefined; +>b : number | undefined + + static c: number | null; +>c : number | null +>null : null + + static d?: number; +>d : number | undefined +} + +// Initializer satisfies strict initialization check + +class C4 { +>C4 : C4 + + a = 0; +>a : number +>0 : 0 + + b: number = 0; +>b : number +>0 : 0 + + c: string = "abc"; +>c : string +>"abc" : "abc" +} + +// Assignment in constructor satisfies strict initialization check + +class C5 { +>C5 : C5 + + a: number; +>a : number + + constructor() { + this.a = 0; +>this.a = 0 : 0 +>this.a : number +>this : this +>a : number +>0 : 0 + } +} + +// All code paths must contain assignment + +class C6 { +>C6 : C6 + + a: number; // Error +>a : number + + constructor(cond: boolean) { +>cond : boolean + + if (cond) { +>cond : boolean + + return; + } + this.a = 0; +>this.a = 0 : 0 +>this.a : number +>this : this +>a : number +>0 : 0 + } +} + +class C7 { +>C7 : C7 + + a: number; +>a : number + + constructor(cond: boolean) { +>cond : boolean + + if (cond) { +>cond : boolean + + this.a = 1; +>this.a = 1 : 1 +>this.a : number +>this : this +>a : number +>1 : 1 + + return; + } + this.a = 0; +>this.a = 0 : 0 +>this.a : number +>this : this +>a : number +>0 : 0 + } +} + +// Properties with string literal names aren't checked + +class C8 { +>C8 : C8 + + a: number; // Error +>a : number + + "b": number; + 0: number; +} + From 85ea473ff3d89ae895293a000544ab4d364deee1 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 17 Nov 2017 13:21:36 -0800 Subject: [PATCH 11/18] Exclude abstract properties from strict initialization checks --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5d80874007..ecf71e114a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -22236,7 +22236,7 @@ namespace ts { } const constructor = findConstructorDeclaration(node); for (const member of node.members) { - if (member.kind === SyntaxKind.PropertyDeclaration && !hasModifier(member, ModifierFlags.Static) && !(member).initializer) { + if (member.kind === SyntaxKind.PropertyDeclaration &&!hasModifier(member, ModifierFlags.Static | ModifierFlags.Abstract) && !(member).initializer) { const propName = (member).name; if (isIdentifier(propName)) { const type = getTypeOfSymbol(getSymbolOfNode(member)); From 20b21d325991fa954378e651df2bea8bd497a722 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 17 Nov 2017 13:21:49 -0800 Subject: [PATCH 12/18] Add test --- .../strictPropertyInitialization.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/cases/conformance/classes/propertyMemberDeclarations/strictPropertyInitialization.ts b/tests/cases/conformance/classes/propertyMemberDeclarations/strictPropertyInitialization.ts index 326e4542d2..9c8d56e1f5 100644 --- a/tests/cases/conformance/classes/propertyMemberDeclarations/strictPropertyInitialization.ts +++ b/tests/cases/conformance/classes/propertyMemberDeclarations/strictPropertyInitialization.ts @@ -75,3 +75,12 @@ class C8 { "b": number; 0: number; } + +// No strict initialization checks for abstract members + +abstract class C9 { + abstract a: number; + abstract b: number | undefined; + abstract c: number | null; + abstract d?: number; +} From 6ba8c57d97afdf78c76c90c8857cf07329eb8893 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 17 Nov 2017 13:22:00 -0800 Subject: [PATCH 13/18] Accept new baselines --- .../strictPropertyInitialization.errors.txt | 9 ++++++++ .../reference/strictPropertyInitialization.js | 21 +++++++++++++++++++ .../strictPropertyInitialization.symbols | 18 ++++++++++++++++ .../strictPropertyInitialization.types | 19 +++++++++++++++++ 4 files changed, 67 insertions(+) diff --git a/tests/baselines/reference/strictPropertyInitialization.errors.txt b/tests/baselines/reference/strictPropertyInitialization.errors.txt index 6d13ea3646..9dfe2b4ae9 100644 --- a/tests/baselines/reference/strictPropertyInitialization.errors.txt +++ b/tests/baselines/reference/strictPropertyInitialization.errors.txt @@ -87,4 +87,13 @@ tests/cases/conformance/classes/propertyMemberDeclarations/strictPropertyInitial "b": number; 0: number; } + + // No strict initialization checks for abstract members + + abstract class C9 { + abstract a: number; + abstract b: number | undefined; + abstract c: number | null; + abstract d?: number; + } \ No newline at end of file diff --git a/tests/baselines/reference/strictPropertyInitialization.js b/tests/baselines/reference/strictPropertyInitialization.js index 7379e5a157..04562b852d 100644 --- a/tests/baselines/reference/strictPropertyInitialization.js +++ b/tests/baselines/reference/strictPropertyInitialization.js @@ -73,6 +73,15 @@ class C8 { "b": number; 0: number; } + +// No strict initialization checks for abstract members + +abstract class C9 { + abstract a: number; + abstract b: number | undefined; + abstract c: number | null; + abstract d?: number; +} //// [strictPropertyInitialization.js] @@ -131,6 +140,12 @@ var C8 = /** @class */ (function () { } return C8; }()); +// No strict initialization checks for abstract members +var C9 = /** @class */ (function () { + function C9() { + } + return C9; +}()); //// [strictPropertyInitialization.d.ts] @@ -174,3 +189,9 @@ declare class C8 { "b": number; 0: number; } +declare abstract class C9 { + abstract a: number; + abstract b: number | undefined; + abstract c: number | null; + abstract d?: number; +} diff --git a/tests/baselines/reference/strictPropertyInitialization.symbols b/tests/baselines/reference/strictPropertyInitialization.symbols index 54b365732c..684eed916a 100644 --- a/tests/baselines/reference/strictPropertyInitialization.symbols +++ b/tests/baselines/reference/strictPropertyInitialization.symbols @@ -145,3 +145,21 @@ class C8 { 0: number; } +// No strict initialization checks for abstract members + +abstract class C9 { +>C9 : Symbol(C9, Decl(strictPropertyInitialization.ts, 73, 1)) + + abstract a: number; +>a : Symbol(C9.a, Decl(strictPropertyInitialization.ts, 77, 19)) + + abstract b: number | undefined; +>b : Symbol(C9.b, Decl(strictPropertyInitialization.ts, 78, 23)) + + abstract c: number | null; +>c : Symbol(C9.c, Decl(strictPropertyInitialization.ts, 79, 35)) + + abstract d?: number; +>d : Symbol(C9.d, Decl(strictPropertyInitialization.ts, 80, 30)) +} + diff --git a/tests/baselines/reference/strictPropertyInitialization.types b/tests/baselines/reference/strictPropertyInitialization.types index 6c1e35908a..ad73b895e8 100644 --- a/tests/baselines/reference/strictPropertyInitialization.types +++ b/tests/baselines/reference/strictPropertyInitialization.types @@ -159,3 +159,22 @@ class C8 { 0: number; } +// No strict initialization checks for abstract members + +abstract class C9 { +>C9 : C9 + + abstract a: number; +>a : number + + abstract b: number | undefined; +>b : number | undefined + + abstract c: number | null; +>c : number | null +>null : null + + abstract d?: number; +>d : number | undefined +} + From 86871c7ea0640ce8be34b83a0a84537bfd05ebe2 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 17 Nov 2017 13:23:19 -0800 Subject: [PATCH 14/18] Fix lint error --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ecf71e114a..4e58e1136c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -22236,7 +22236,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 (member.kind === SyntaxKind.PropertyDeclaration && !hasModifier(member, ModifierFlags.Static | ModifierFlags.Abstract) && !(member).initializer) { const propName = (member).name; if (isIdentifier(propName)) { const type = getTypeOfSymbol(getSymbolOfNode(member)); From dccf57f107d7e870e5f4018cad4f67ffc444f6b3 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 18 Nov 2017 08:49:24 -0800 Subject: [PATCH 15/18] Simplify checkPropertyAccessExpressionOrQualifiedName --- src/compiler/checker.ts | 76 +++++++++++++++++------------------------ 1 file changed, 31 insertions(+), 45 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f444cd105f..bb5ea71548 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15562,63 +15562,49 @@ namespace ts { } function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, right: Identifier) { - const type = checkNonNullExpression(left); - if (isTypeAny(type) || type === silentNeverType) { - return type; - } - - const apparentType = getApparentType(getWidenedType(type)); - if (apparentType === unknownType || (type.flags & TypeFlags.TypeParameter && isTypeAny(apparentType))) { - // handle cases when type is Type parameter with invalid or any constraint + let propType: Type; + const leftType = checkNonNullExpression(left); + const apparentType = getApparentType(getWidenedType(leftType)); + if (isTypeAny(apparentType) || apparentType === silentNeverType) { return apparentType; } + const assignmentKind = getAssignmentTargetKind(node); const prop = getPropertyOfType(apparentType, right.escapedText); if (!prop) { const indexInfo = getIndexInfoOfType(apparentType, IndexKind.String); - if (indexInfo && indexInfo.type) { - if (indexInfo.isReadonly && (isAssignmentTarget(node) || isDeleteTarget(node))) { - error(node, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(apparentType)); + if (!(indexInfo && indexInfo.type)) { + if (right.escapedText && !checkAndReportErrorForExtendingInterface(node)) { + reportNonexistentProperty(right, leftType.flags & TypeFlags.TypeParameter && (leftType as TypeParameter).isThisType ? apparentType : leftType); } - return getFlowTypeOfPropertyAccess(node, /*prop*/ undefined, indexInfo.type, getAssignmentTargetKind(node)); - } - if (right.escapedText && !checkAndReportErrorForExtendingInterface(node)) { - reportNonexistentProperty(right, type.flags & TypeFlags.TypeParameter && (type as TypeParameter).isThisType ? apparentType : type); - } - return unknownType; - } - - checkPropertyNotUsedBeforeDeclaration(prop, node, right); - - markPropertyAsReferenced(prop, node, left.kind === SyntaxKind.ThisKeyword); - - getNodeLinks(node).resolvedSymbol = prop; - - checkPropertyAccessibility(node, left, apparentType, prop); - - const propType = getDeclaredOrApparentType(prop, node); - const assignmentKind = getAssignmentTargetKind(node); - - if (assignmentKind) { - if (isReferenceToReadonlyEntity(node, prop) || isReferenceThroughNamespaceImport(node)) { - error(right, Diagnostics.Cannot_assign_to_0_because_it_is_a_constant_or_a_read_only_property, idText(right)); return unknownType; } + if (indexInfo.isReadonly && (isAssignmentTarget(node) || isDeleteTarget(node))) { + error(node, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(apparentType)); + } + propType = indexInfo.type; } - return getFlowTypeOfPropertyAccess(node, prop, propType, assignmentKind); - } - - /** - * Only compute control flow type if this is a property access expression that isn't an - * assignment target, and the referenced property was declared as a variable, property, - * accessor, or optional method. - */ - function getFlowTypeOfPropertyAccess(node: PropertyAccessExpression | QualifiedName, prop: Symbol | undefined, type: Type, assignmentKind: AssignmentKind) { + else { + checkPropertyNotUsedBeforeDeclaration(prop, node, right); + markPropertyAsReferenced(prop, node, left.kind === SyntaxKind.ThisKeyword); + getNodeLinks(node).resolvedSymbol = prop; + checkPropertyAccessibility(node, left, apparentType, prop); + if (assignmentKind) { + if (isReferenceToReadonlyEntity(node, prop) || isReferenceThroughNamespaceImport(node)) { + error(right, Diagnostics.Cannot_assign_to_0_because_it_is_a_constant_or_a_read_only_property, idText(right)); + return unknownType; + } + } + propType = getDeclaredOrApparentType(prop, node); + } + // Only compute control flow type if this is a property access expression that isn't an + // assignment target, and the referenced property was declared as a variable, property, + // accessor, or optional method. if (node.kind !== SyntaxKind.PropertyAccessExpression || assignmentKind === AssignmentKind.Definite || - prop && !(prop.flags & (SymbolFlags.Variable | SymbolFlags.Property | SymbolFlags.Accessor)) && !(prop.flags & SymbolFlags.Method && type.flags & TypeFlags.Union)) { - return type; + prop && !(prop.flags & (SymbolFlags.Variable | SymbolFlags.Property | SymbolFlags.Accessor)) && !(prop.flags & SymbolFlags.Method && propType.flags & TypeFlags.Union)) { + return propType; } - const flowType = getFlowTypeOfReference(node, type); + const flowType = getFlowTypeOfReference(node, propType); return assignmentKind ? getBaseTypeOfLiteralType(flowType) : flowType; } From 0fbf36c2fd86b010d043f9d3b9933f02628fdaad Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 18 Nov 2017 11:32:17 -0800 Subject: [PATCH 16/18] 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 From 82fd5a884dbd4d058736981635198ba27e4d656d Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 18 Nov 2017 11:42:56 -0800 Subject: [PATCH 17/18] Add test --- .../strictPropertyInitialization.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/cases/conformance/classes/propertyMemberDeclarations/strictPropertyInitialization.ts b/tests/cases/conformance/classes/propertyMemberDeclarations/strictPropertyInitialization.ts index 9c8d56e1f5..0117720042 100644 --- a/tests/cases/conformance/classes/propertyMemberDeclarations/strictPropertyInitialization.ts +++ b/tests/cases/conformance/classes/propertyMemberDeclarations/strictPropertyInitialization.ts @@ -84,3 +84,18 @@ abstract class C9 { abstract c: number | null; abstract d?: number; } + +// Properties with non-undefined types must be assigned before they can be accessed +// within their constructor + +class C10 { + a: number; + b: number; + c?: number; + constructor() { + let x = this.a; // Error + this.a = this.b; // Error + this.b = x; + let y = this.c; + } +} From 87a8d41e11fabfe319b2d4e4f7b27ed3774c63c2 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 18 Nov 2017 11:43:04 -0800 Subject: [PATCH 18/18] Accept new baselines --- .../strictPropertyInitialization.errors.txt | 23 +++++++++- .../reference/strictPropertyInitialization.js | 32 +++++++++++++ .../strictPropertyInitialization.symbols | 44 ++++++++++++++++++ .../strictPropertyInitialization.types | 46 +++++++++++++++++++ 4 files changed, 144 insertions(+), 1 deletion(-) diff --git a/tests/baselines/reference/strictPropertyInitialization.errors.txt b/tests/baselines/reference/strictPropertyInitialization.errors.txt index 9dfe2b4ae9..4a3fe0eb3e 100644 --- a/tests/baselines/reference/strictPropertyInitialization.errors.txt +++ b/tests/baselines/reference/strictPropertyInitialization.errors.txt @@ -2,9 +2,11 @@ tests/cases/conformance/classes/propertyMemberDeclarations/strictPropertyInitial tests/cases/conformance/classes/propertyMemberDeclarations/strictPropertyInitialization.ts(6,5): error TS2564: Property 'c' has no initializer and is not definitely assigned in the constructor. tests/cases/conformance/classes/propertyMemberDeclarations/strictPropertyInitialization.ts(48,5): error TS2564: Property 'a' has no initializer and is not definitely assigned in the constructor. tests/cases/conformance/classes/propertyMemberDeclarations/strictPropertyInitialization.ts(71,5): error TS2564: Property 'a' has no initializer and is not definitely assigned in the constructor. +tests/cases/conformance/classes/propertyMemberDeclarations/strictPropertyInitialization.ts(93,22): error TS2565: Property 'a' is used before being assigned. +tests/cases/conformance/classes/propertyMemberDeclarations/strictPropertyInitialization.ts(94,23): error TS2565: Property 'b' is used before being assigned. -==== tests/cases/conformance/classes/propertyMemberDeclarations/strictPropertyInitialization.ts (4 errors) ==== +==== tests/cases/conformance/classes/propertyMemberDeclarations/strictPropertyInitialization.ts (6 errors) ==== // Properties with non-undefined types require initialization class C1 { @@ -96,4 +98,23 @@ tests/cases/conformance/classes/propertyMemberDeclarations/strictPropertyInitial abstract c: number | null; abstract d?: number; } + + // Properties with non-undefined types must be assigned before they can be accessed + // within their constructor + + class C10 { + a: number; + b: number; + c?: number; + constructor() { + let x = this.a; // Error + ~ +!!! error TS2565: Property 'a' is used before being assigned. + this.a = this.b; // Error + ~ +!!! error TS2565: Property 'b' is used before being assigned. + this.b = x; + let y = this.c; + } + } \ No newline at end of file diff --git a/tests/baselines/reference/strictPropertyInitialization.js b/tests/baselines/reference/strictPropertyInitialization.js index 04562b852d..6de5084e40 100644 --- a/tests/baselines/reference/strictPropertyInitialization.js +++ b/tests/baselines/reference/strictPropertyInitialization.js @@ -82,6 +82,21 @@ abstract class C9 { abstract c: number | null; abstract d?: number; } + +// Properties with non-undefined types must be assigned before they can be accessed +// within their constructor + +class C10 { + a: number; + b: number; + c?: number; + constructor() { + let x = this.a; // Error + this.a = this.b; // Error + this.b = x; + let y = this.c; + } +} //// [strictPropertyInitialization.js] @@ -146,6 +161,17 @@ var C9 = /** @class */ (function () { } return C9; }()); +// Properties with non-undefined types must be assigned before they can be accessed +// within their constructor +var C10 = /** @class */ (function () { + function C10() { + var x = this.a; // Error + this.a = this.b; // Error + this.b = x; + var y = this.c; + } + return C10; +}()); //// [strictPropertyInitialization.d.ts] @@ -195,3 +221,9 @@ declare abstract class C9 { abstract c: number | null; abstract d?: number; } +declare class C10 { + a: number; + b: number; + c?: number; + constructor(); +} diff --git a/tests/baselines/reference/strictPropertyInitialization.symbols b/tests/baselines/reference/strictPropertyInitialization.symbols index 684eed916a..3247d975bb 100644 --- a/tests/baselines/reference/strictPropertyInitialization.symbols +++ b/tests/baselines/reference/strictPropertyInitialization.symbols @@ -163,3 +163,47 @@ abstract class C9 { >d : Symbol(C9.d, Decl(strictPropertyInitialization.ts, 80, 30)) } +// Properties with non-undefined types must be assigned before they can be accessed +// within their constructor + +class C10 { +>C10 : Symbol(C10, Decl(strictPropertyInitialization.ts, 82, 1)) + + a: number; +>a : Symbol(C10.a, Decl(strictPropertyInitialization.ts, 87, 11)) + + b: number; +>b : Symbol(C10.b, Decl(strictPropertyInitialization.ts, 88, 14)) + + c?: number; +>c : Symbol(C10.c, Decl(strictPropertyInitialization.ts, 89, 14)) + + constructor() { + let x = this.a; // Error +>x : Symbol(x, Decl(strictPropertyInitialization.ts, 92, 11)) +>this.a : Symbol(C10.a, Decl(strictPropertyInitialization.ts, 87, 11)) +>this : Symbol(C10, Decl(strictPropertyInitialization.ts, 82, 1)) +>a : Symbol(C10.a, Decl(strictPropertyInitialization.ts, 87, 11)) + + this.a = this.b; // Error +>this.a : Symbol(C10.a, Decl(strictPropertyInitialization.ts, 87, 11)) +>this : Symbol(C10, Decl(strictPropertyInitialization.ts, 82, 1)) +>a : Symbol(C10.a, Decl(strictPropertyInitialization.ts, 87, 11)) +>this.b : Symbol(C10.b, Decl(strictPropertyInitialization.ts, 88, 14)) +>this : Symbol(C10, Decl(strictPropertyInitialization.ts, 82, 1)) +>b : Symbol(C10.b, Decl(strictPropertyInitialization.ts, 88, 14)) + + this.b = x; +>this.b : Symbol(C10.b, Decl(strictPropertyInitialization.ts, 88, 14)) +>this : Symbol(C10, Decl(strictPropertyInitialization.ts, 82, 1)) +>b : Symbol(C10.b, Decl(strictPropertyInitialization.ts, 88, 14)) +>x : Symbol(x, Decl(strictPropertyInitialization.ts, 92, 11)) + + let y = this.c; +>y : Symbol(y, Decl(strictPropertyInitialization.ts, 95, 11)) +>this.c : Symbol(C10.c, Decl(strictPropertyInitialization.ts, 89, 14)) +>this : Symbol(C10, Decl(strictPropertyInitialization.ts, 82, 1)) +>c : Symbol(C10.c, Decl(strictPropertyInitialization.ts, 89, 14)) + } +} + diff --git a/tests/baselines/reference/strictPropertyInitialization.types b/tests/baselines/reference/strictPropertyInitialization.types index ad73b895e8..24cbdb14ef 100644 --- a/tests/baselines/reference/strictPropertyInitialization.types +++ b/tests/baselines/reference/strictPropertyInitialization.types @@ -178,3 +178,49 @@ abstract class C9 { >d : number | undefined } +// Properties with non-undefined types must be assigned before they can be accessed +// within their constructor + +class C10 { +>C10 : C10 + + a: number; +>a : number + + b: number; +>b : number + + c?: number; +>c : number | undefined + + constructor() { + let x = this.a; // Error +>x : number +>this.a : number +>this : this +>a : number + + this.a = this.b; // Error +>this.a = this.b : number +>this.a : number +>this : this +>a : number +>this.b : number +>this : this +>b : number + + this.b = x; +>this.b = x : number +>this.b : number +>this : this +>b : number +>x : number + + let y = this.c; +>y : number | undefined +>this.c : number | undefined +>this : this +>c : number | undefined + } +} +