Skip to content

Commit 9abb72d

Browse files
authored
Merge pull request #20166 from Microsoft/definiteAssignmentAssertions
Definite assignment assertions
2 parents cc7b46b + 9b9f3f2 commit 9abb72d

11 files changed

+711
-9
lines changed

src/compiler/checker.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13123,8 +13123,10 @@ namespace ts {
1312313123
// the entire control flow graph from the variable's declaration (i.e. when the flow container and
1312413124
// declaration container are the same).
1312513125
const assumeInitialized = isParameter || isAlias || isOuterVariable ||
13126-
type !== autoType && type !== autoArrayType && (!strictNullChecks || (type.flags & TypeFlags.Any) !== 0 || isInTypeQuery(node) || node.parent.kind === SyntaxKind.ExportSpecifier) ||
13126+
type !== autoType && type !== autoArrayType && (!strictNullChecks || (type.flags & TypeFlags.Any) !== 0 ||
13127+
isInTypeQuery(node) || node.parent.kind === SyntaxKind.ExportSpecifier) ||
1312713128
node.parent.kind === SyntaxKind.NonNullExpression ||
13129+
declaration.kind === SyntaxKind.VariableDeclaration && (<VariableDeclaration>declaration).exclamationToken ||
1312813130
declaration.flags & NodeFlags.Ambient;
1312913131
const initialType = assumeInitialized ? (isParameter ? removeOptionalityFromDeclaredType(type, getRootDeclaration(declaration) as VariableLikeDeclaration) : type) :
1313013132
type === autoType || type === autoArrayType ? undefinedType :
@@ -22671,6 +22673,7 @@ namespace ts {
2267122673
function isInstancePropertyWithoutInitializer(node: Node) {
2267222674
return node.kind === SyntaxKind.PropertyDeclaration &&
2267322675
!hasModifier(node, ModifierFlags.Static | ModifierFlags.Abstract) &&
22676+
!(<PropertyDeclaration>node).exclamationToken &&
2267422677
!(<PropertyDeclaration>node).initializer;
2267522678
}
2267622679

@@ -26103,6 +26106,10 @@ namespace ts {
2610326106
}
2610426107
}
2610526108

26109+
if (node.exclamationToken && (node.parent.parent.kind !== SyntaxKind.VariableStatement || !node.type || node.initializer || node.flags & NodeFlags.Ambient)) {
26110+
return grammarErrorOnNode(node.exclamationToken, Diagnostics.A_definite_assignment_assertion_is_not_permitted_in_this_context);
26111+
}
26112+
2610626113
if (compilerOptions.module !== ModuleKind.ES2015 && compilerOptions.module !== ModuleKind.ESNext && compilerOptions.module !== ModuleKind.System && !compilerOptions.noEmit &&
2610726114
!(node.parent.parent.flags & NodeFlags.Ambient) && hasModifier(node.parent.parent, ModifierFlags.Export)) {
2610826115
checkESModuleMarker(node.name);
@@ -26266,6 +26273,11 @@ namespace ts {
2626626273
if (node.flags & NodeFlags.Ambient && node.initializer) {
2626726274
return grammarErrorOnFirstToken(node.initializer, Diagnostics.Initializers_are_not_allowed_in_ambient_contexts);
2626826275
}
26276+
26277+
if (node.exclamationToken && (!isClassLike(node.parent) || !node.type || node.initializer ||
26278+
node.flags & NodeFlags.Ambient || hasModifier(node, ModifierFlags.Static | ModifierFlags.Abstract))) {
26279+
return grammarErrorOnNode(node.exclamationToken, Diagnostics.A_definite_assignment_assertion_is_not_permitted_in_this_context);
26280+
}
2626926281
}
2627026282

2627126283
function checkGrammarTopLevelElementForRequiredDeclareModifier(node: Node): boolean {

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -831,6 +831,10 @@
831831
"category": "Error",
832832
"code": 1254
833833
},
834+
"A definite assignment assertion '!' is not permitted in this context.": {
835+
"category": "Error",
836+
"code": 1255
837+
},
834838
"'with' statements are not allowed in an async function block.": {
835839
"category": "Error",
836840
"code": 1300

src/compiler/parser.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ namespace ts {
9999
visitNode(cbNode, (<VariableLikeDeclaration>node).dotDotDotToken) ||
100100
visitNode(cbNode, (<VariableLikeDeclaration>node).name) ||
101101
visitNode(cbNode, (<VariableLikeDeclaration>node).questionToken) ||
102+
visitNode(cbNode, (<VariableLikeDeclaration>node).exclamationToken) ||
102103
visitNode(cbNode, (<VariableLikeDeclaration>node).type) ||
103104
visitNode(cbNode, (<VariableLikeDeclaration>node).initializer);
104105
case SyntaxKind.FunctionType:
@@ -5251,9 +5252,17 @@ namespace ts {
52515252
return parseIdentifier();
52525253
}
52535254

5254-
function parseVariableDeclaration(): VariableDeclaration {
5255+
function parseVariableDeclarationAllowExclamation() {
5256+
return parseVariableDeclaration(/*allowExclamation*/ true);
5257+
}
5258+
5259+
function parseVariableDeclaration(allowExclamation?: boolean): VariableDeclaration {
52555260
const node = <VariableDeclaration>createNode(SyntaxKind.VariableDeclaration);
52565261
node.name = parseIdentifierOrPattern();
5262+
if (allowExclamation && node.name.kind === SyntaxKind.Identifier &&
5263+
token() === SyntaxKind.ExclamationToken && !scanner.hasPrecedingLineBreak()) {
5264+
node.exclamationToken = parseTokenNode();
5265+
}
52575266
node.type = parseTypeAnnotation();
52585267
if (!isInOrOfKeyword(token())) {
52595268
node.initializer = parseInitializer();
@@ -5295,7 +5304,8 @@ namespace ts {
52955304
const savedDisallowIn = inDisallowInContext();
52965305
setDisallowInContext(inForStatementInitializer);
52975306

5298-
node.declarations = parseDelimitedList(ParsingContext.VariableDeclarations, parseVariableDeclaration);
5307+
node.declarations = parseDelimitedList(ParsingContext.VariableDeclarations,
5308+
inForStatementInitializer ? parseVariableDeclaration : parseVariableDeclarationAllowExclamation);
52995309

53005310
setDisallowInContext(savedDisallowIn);
53015311
}
@@ -5346,6 +5356,9 @@ namespace ts {
53465356

53475357
function parsePropertyDeclaration(node: PropertyDeclaration): PropertyDeclaration {
53485358
node.kind = SyntaxKind.PropertyDeclaration;
5359+
if (!node.questionToken && token() === SyntaxKind.ExclamationToken && !scanner.hasPrecedingLineBreak()) {
5360+
node.exclamationToken = parseTokenNode();
5361+
}
53495362
node.type = parseTypeAnnotation();
53505363

53515364
// For instance properties specifically, since they are evaluated inside the constructor,

src/compiler/types.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,7 @@ namespace ts {
598598

599599
export type DotDotDotToken = Token<SyntaxKind.DotDotDotToken>;
600600
export type QuestionToken = Token<SyntaxKind.QuestionToken>;
601+
export type ExclamationToken = Token<SyntaxKind.ExclamationToken>;
601602
export type ColonToken = Token<SyntaxKind.ColonToken>;
602603
export type EqualsToken = Token<SyntaxKind.EqualsToken>;
603604
export type AsteriskToken = Token<SyntaxKind.AsteriskToken>;
@@ -761,9 +762,10 @@ namespace ts {
761762
export interface VariableDeclaration extends NamedDeclaration {
762763
kind: SyntaxKind.VariableDeclaration;
763764
parent?: VariableDeclarationList | CatchClause;
764-
name: BindingName; // Declared variable name
765-
type?: TypeNode; // Optional type annotation
766-
initializer?: Expression; // Optional initializer
765+
name: BindingName; // Declared variable name
766+
exclamationToken?: ExclamationToken; // Optional definite assignment assertion
767+
type?: TypeNode; // Optional type annotation
768+
initializer?: Expression; // Optional initializer
767769
}
768770

769771
export interface VariableDeclarationList extends Node {
@@ -801,8 +803,9 @@ namespace ts {
801803

802804
export interface PropertyDeclaration extends ClassElement, JSDocContainer {
803805
kind: SyntaxKind.PropertyDeclaration;
804-
questionToken?: QuestionToken; // Present for use with reporting a grammar error
805806
name: PropertyName;
807+
questionToken?: QuestionToken; // Present for use with reporting a grammar error
808+
exclamationToken?: ExclamationToken;
806809
type?: TypeNode;
807810
initializer?: Expression; // Optional initializer
808811
}
@@ -860,6 +863,7 @@ namespace ts {
860863
dotDotDotToken?: DotDotDotToken;
861864
name: DeclarationName;
862865
questionToken?: QuestionToken;
866+
exclamationToken?: ExclamationToken;
863867
type?: TypeNode;
864868
initializer?: Expression;
865869
}

tests/baselines/reference/api/tsserverlibrary.d.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,7 @@ declare namespace ts {
461461
}
462462
type DotDotDotToken = Token<SyntaxKind.DotDotDotToken>;
463463
type QuestionToken = Token<SyntaxKind.QuestionToken>;
464+
type ExclamationToken = Token<SyntaxKind.ExclamationToken>;
464465
type ColonToken = Token<SyntaxKind.ColonToken>;
465466
type EqualsToken = Token<SyntaxKind.EqualsToken>;
466467
type AsteriskToken = Token<SyntaxKind.AsteriskToken>;
@@ -537,6 +538,7 @@ declare namespace ts {
537538
kind: SyntaxKind.VariableDeclaration;
538539
parent?: VariableDeclarationList | CatchClause;
539540
name: BindingName;
541+
exclamationToken?: ExclamationToken;
540542
type?: TypeNode;
541543
initializer?: Expression;
542544
}
@@ -571,8 +573,9 @@ declare namespace ts {
571573
}
572574
interface PropertyDeclaration extends ClassElement, JSDocContainer {
573575
kind: SyntaxKind.PropertyDeclaration;
574-
questionToken?: QuestionToken;
575576
name: PropertyName;
577+
questionToken?: QuestionToken;
578+
exclamationToken?: ExclamationToken;
576579
type?: TypeNode;
577580
initializer?: Expression;
578581
}
@@ -606,6 +609,7 @@ declare namespace ts {
606609
dotDotDotToken?: DotDotDotToken;
607610
name: DeclarationName;
608611
questionToken?: QuestionToken;
612+
exclamationToken?: ExclamationToken;
609613
type?: TypeNode;
610614
initializer?: Expression;
611615
}

tests/baselines/reference/api/typescript.d.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,7 @@ declare namespace ts {
461461
}
462462
type DotDotDotToken = Token<SyntaxKind.DotDotDotToken>;
463463
type QuestionToken = Token<SyntaxKind.QuestionToken>;
464+
type ExclamationToken = Token<SyntaxKind.ExclamationToken>;
464465
type ColonToken = Token<SyntaxKind.ColonToken>;
465466
type EqualsToken = Token<SyntaxKind.EqualsToken>;
466467
type AsteriskToken = Token<SyntaxKind.AsteriskToken>;
@@ -537,6 +538,7 @@ declare namespace ts {
537538
kind: SyntaxKind.VariableDeclaration;
538539
parent?: VariableDeclarationList | CatchClause;
539540
name: BindingName;
541+
exclamationToken?: ExclamationToken;
540542
type?: TypeNode;
541543
initializer?: Expression;
542544
}
@@ -571,8 +573,9 @@ declare namespace ts {
571573
}
572574
interface PropertyDeclaration extends ClassElement, JSDocContainer {
573575
kind: SyntaxKind.PropertyDeclaration;
574-
questionToken?: QuestionToken;
575576
name: PropertyName;
577+
questionToken?: QuestionToken;
578+
exclamationToken?: ExclamationToken;
576579
type?: TypeNode;
577580
initializer?: Expression;
578581
}
@@ -606,6 +609,7 @@ declare namespace ts {
606609
dotDotDotToken?: DotDotDotToken;
607610
name: DeclarationName;
608611
questionToken?: QuestionToken;
612+
exclamationToken?: ExclamationToken;
609613
type?: TypeNode;
610614
initializer?: Expression;
611615
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
tests/cases/conformance/controlFlow/definiteAssignmentAssertions.ts(5,5): error TS2564: Property 'b' has no initializer and is not definitely assigned in the constructor.
2+
tests/cases/conformance/controlFlow/definiteAssignmentAssertions.ts(20,6): error TS1255: A definite assignment assertion '!' is not permitted in this context.
3+
tests/cases/conformance/controlFlow/definiteAssignmentAssertions.ts(21,6): error TS1255: A definite assignment assertion '!' is not permitted in this context.
4+
tests/cases/conformance/controlFlow/definiteAssignmentAssertions.ts(22,13): error TS1255: A definite assignment assertion '!' is not permitted in this context.
5+
tests/cases/conformance/controlFlow/definiteAssignmentAssertions.ts(28,6): error TS1255: A definite assignment assertion '!' is not permitted in this context.
6+
tests/cases/conformance/controlFlow/definiteAssignmentAssertions.ts(34,15): error TS1255: A definite assignment assertion '!' is not permitted in this context.
7+
tests/cases/conformance/controlFlow/definiteAssignmentAssertions.ts(68,10): error TS1255: A definite assignment assertion '!' is not permitted in this context.
8+
tests/cases/conformance/controlFlow/definiteAssignmentAssertions.ts(69,10): error TS1255: A definite assignment assertion '!' is not permitted in this context.
9+
tests/cases/conformance/controlFlow/definiteAssignmentAssertions.ts(70,10): error TS1255: A definite assignment assertion '!' is not permitted in this context.
10+
tests/cases/conformance/controlFlow/definiteAssignmentAssertions.ts(75,15): error TS1255: A definite assignment assertion '!' is not permitted in this context.
11+
tests/cases/conformance/controlFlow/definiteAssignmentAssertions.ts(76,15): error TS1255: A definite assignment assertion '!' is not permitted in this context.
12+
13+
14+
==== tests/cases/conformance/controlFlow/definiteAssignmentAssertions.ts (11 errors) ====
15+
// Suppress strict property initialization check
16+
17+
class C1 {
18+
a!: number;
19+
b: string; // Error
20+
~
21+
!!! error TS2564: Property 'b' has no initializer and is not definitely assigned in the constructor.
22+
}
23+
24+
// Suppress definite assignment check in constructor
25+
26+
class C2 {
27+
a!: number;
28+
constructor() {
29+
let x = this.a;
30+
}
31+
}
32+
33+
// Definite assignment assertion requires type annotation, no initializer, no static modifier
34+
35+
class C3 {
36+
a! = 1;
37+
~
38+
!!! error TS1255: A definite assignment assertion '!' is not permitted in this context.
39+
b!: number = 1;
40+
~
41+
!!! error TS1255: A definite assignment assertion '!' is not permitted in this context.
42+
static c!: number;
43+
~
44+
!!! error TS1255: A definite assignment assertion '!' is not permitted in this context.
45+
}
46+
47+
// Definite assignment assertion not permitted in ambient context
48+
49+
declare class C4 {
50+
a!: number;
51+
~
52+
!!! error TS1255: A definite assignment assertion '!' is not permitted in this context.
53+
}
54+
55+
// Definite assignment assertion not permitted on abstract property
56+
57+
abstract class C5 {
58+
abstract a!: number;
59+
~
60+
!!! error TS1255: A definite assignment assertion '!' is not permitted in this context.
61+
}
62+
63+
// Suppress definite assignment check for variable
64+
65+
function f1() {
66+
let x!: number;
67+
let y = x;
68+
var a!: number;
69+
var b = a;
70+
}
71+
72+
function f2() {
73+
let x!: string | number;
74+
if (typeof x === "string") {
75+
let s: string = x;
76+
}
77+
else {
78+
let n: number = x;
79+
}
80+
}
81+
82+
function f3() {
83+
let x!: number;
84+
const g = () => {
85+
x = 1;
86+
}
87+
g();
88+
let y = x;
89+
}
90+
91+
// Definite assignment assertion requires type annotation and no initializer
92+
93+
function f4() {
94+
let a!;
95+
~
96+
!!! error TS1255: A definite assignment assertion '!' is not permitted in this context.
97+
let b! = 1;
98+
~
99+
!!! error TS1255: A definite assignment assertion '!' is not permitted in this context.
100+
let c!: number = 1;
101+
~
102+
!!! error TS1255: A definite assignment assertion '!' is not permitted in this context.
103+
}
104+
105+
// Definite assignment assertion not permitted in ambient context
106+
107+
declare let v1!: number;
108+
~
109+
!!! error TS1255: A definite assignment assertion '!' is not permitted in this context.
110+
declare var v2!: number;
111+
~
112+
!!! error TS1255: A definite assignment assertion '!' is not permitted in this context.
113+

0 commit comments

Comments
 (0)