Skip to content

Commit

Permalink
Merge pull request #20166 from Microsoft/definiteAssignmentAssertions
Browse files Browse the repository at this point in the history
Definite assignment assertions
  • Loading branch information
ahejlsberg authored Nov 21, 2017
2 parents cc7b46b + 9b9f3f2 commit 9abb72d
Show file tree
Hide file tree
Showing 11 changed files with 711 additions and 9 deletions.
14 changes: 13 additions & 1 deletion src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13123,8 +13123,10 @@ namespace ts {
// the entire control flow graph from the variable's declaration (i.e. when the flow container and
// declaration container are the same).
const assumeInitialized = isParameter || isAlias || isOuterVariable ||
type !== autoType && type !== autoArrayType && (!strictNullChecks || (type.flags & TypeFlags.Any) !== 0 || isInTypeQuery(node) || node.parent.kind === SyntaxKind.ExportSpecifier) ||
type !== autoType && type !== autoArrayType && (!strictNullChecks || (type.flags & TypeFlags.Any) !== 0 ||
isInTypeQuery(node) || node.parent.kind === SyntaxKind.ExportSpecifier) ||
node.parent.kind === SyntaxKind.NonNullExpression ||
declaration.kind === SyntaxKind.VariableDeclaration && (<VariableDeclaration>declaration).exclamationToken ||
declaration.flags & NodeFlags.Ambient;
const initialType = assumeInitialized ? (isParameter ? removeOptionalityFromDeclaredType(type, getRootDeclaration(declaration) as VariableLikeDeclaration) : type) :
type === autoType || type === autoArrayType ? undefinedType :
Expand Down Expand Up @@ -22671,6 +22673,7 @@ namespace ts {
function isInstancePropertyWithoutInitializer(node: Node) {
return node.kind === SyntaxKind.PropertyDeclaration &&
!hasModifier(node, ModifierFlags.Static | ModifierFlags.Abstract) &&
!(<PropertyDeclaration>node).exclamationToken &&
!(<PropertyDeclaration>node).initializer;
}

Expand Down Expand Up @@ -26103,6 +26106,10 @@ namespace ts {
}
}

if (node.exclamationToken && (node.parent.parent.kind !== SyntaxKind.VariableStatement || !node.type || node.initializer || node.flags & NodeFlags.Ambient)) {
return grammarErrorOnNode(node.exclamationToken, Diagnostics.A_definite_assignment_assertion_is_not_permitted_in_this_context);
}

if (compilerOptions.module !== ModuleKind.ES2015 && compilerOptions.module !== ModuleKind.ESNext && compilerOptions.module !== ModuleKind.System && !compilerOptions.noEmit &&
!(node.parent.parent.flags & NodeFlags.Ambient) && hasModifier(node.parent.parent, ModifierFlags.Export)) {
checkESModuleMarker(node.name);
Expand Down Expand Up @@ -26266,6 +26273,11 @@ namespace ts {
if (node.flags & NodeFlags.Ambient && node.initializer) {
return grammarErrorOnFirstToken(node.initializer, Diagnostics.Initializers_are_not_allowed_in_ambient_contexts);
}

if (node.exclamationToken && (!isClassLike(node.parent) || !node.type || node.initializer ||
node.flags & NodeFlags.Ambient || hasModifier(node, ModifierFlags.Static | ModifierFlags.Abstract))) {
return grammarErrorOnNode(node.exclamationToken, Diagnostics.A_definite_assignment_assertion_is_not_permitted_in_this_context);
}
}

function checkGrammarTopLevelElementForRequiredDeclareModifier(node: Node): boolean {
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,10 @@
"category": "Error",
"code": 1254
},
"A definite assignment assertion '!' is not permitted in this context.": {
"category": "Error",
"code": 1255
},
"'with' statements are not allowed in an async function block.": {
"category": "Error",
"code": 1300
Expand Down
17 changes: 15 additions & 2 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ namespace ts {
visitNode(cbNode, (<VariableLikeDeclaration>node).dotDotDotToken) ||
visitNode(cbNode, (<VariableLikeDeclaration>node).name) ||
visitNode(cbNode, (<VariableLikeDeclaration>node).questionToken) ||
visitNode(cbNode, (<VariableLikeDeclaration>node).exclamationToken) ||
visitNode(cbNode, (<VariableLikeDeclaration>node).type) ||
visitNode(cbNode, (<VariableLikeDeclaration>node).initializer);
case SyntaxKind.FunctionType:
Expand Down Expand Up @@ -5251,9 +5252,17 @@ namespace ts {
return parseIdentifier();
}

function parseVariableDeclaration(): VariableDeclaration {
function parseVariableDeclarationAllowExclamation() {
return parseVariableDeclaration(/*allowExclamation*/ true);
}

function parseVariableDeclaration(allowExclamation?: boolean): VariableDeclaration {
const node = <VariableDeclaration>createNode(SyntaxKind.VariableDeclaration);
node.name = parseIdentifierOrPattern();
if (allowExclamation && node.name.kind === SyntaxKind.Identifier &&
token() === SyntaxKind.ExclamationToken && !scanner.hasPrecedingLineBreak()) {
node.exclamationToken = parseTokenNode();
}
node.type = parseTypeAnnotation();
if (!isInOrOfKeyword(token())) {
node.initializer = parseInitializer();
Expand Down Expand Up @@ -5295,7 +5304,8 @@ namespace ts {
const savedDisallowIn = inDisallowInContext();
setDisallowInContext(inForStatementInitializer);

node.declarations = parseDelimitedList(ParsingContext.VariableDeclarations, parseVariableDeclaration);
node.declarations = parseDelimitedList(ParsingContext.VariableDeclarations,
inForStatementInitializer ? parseVariableDeclaration : parseVariableDeclarationAllowExclamation);

setDisallowInContext(savedDisallowIn);
}
Expand Down Expand Up @@ -5346,6 +5356,9 @@ namespace ts {

function parsePropertyDeclaration(node: PropertyDeclaration): PropertyDeclaration {
node.kind = SyntaxKind.PropertyDeclaration;
if (!node.questionToken && token() === SyntaxKind.ExclamationToken && !scanner.hasPrecedingLineBreak()) {
node.exclamationToken = parseTokenNode();
}
node.type = parseTypeAnnotation();

// For instance properties specifically, since they are evaluated inside the constructor,
Expand Down
12 changes: 8 additions & 4 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,7 @@ namespace ts {

export type DotDotDotToken = Token<SyntaxKind.DotDotDotToken>;
export type QuestionToken = Token<SyntaxKind.QuestionToken>;
export type ExclamationToken = Token<SyntaxKind.ExclamationToken>;
export type ColonToken = Token<SyntaxKind.ColonToken>;
export type EqualsToken = Token<SyntaxKind.EqualsToken>;
export type AsteriskToken = Token<SyntaxKind.AsteriskToken>;
Expand Down Expand Up @@ -761,9 +762,10 @@ namespace ts {
export interface VariableDeclaration extends NamedDeclaration {
kind: SyntaxKind.VariableDeclaration;
parent?: VariableDeclarationList | CatchClause;
name: BindingName; // Declared variable name
type?: TypeNode; // Optional type annotation
initializer?: Expression; // Optional initializer
name: BindingName; // Declared variable name
exclamationToken?: ExclamationToken; // Optional definite assignment assertion
type?: TypeNode; // Optional type annotation
initializer?: Expression; // Optional initializer
}

export interface VariableDeclarationList extends Node {
Expand Down Expand Up @@ -801,8 +803,9 @@ namespace ts {

export interface PropertyDeclaration extends ClassElement, JSDocContainer {
kind: SyntaxKind.PropertyDeclaration;
questionToken?: QuestionToken; // Present for use with reporting a grammar error
name: PropertyName;
questionToken?: QuestionToken; // Present for use with reporting a grammar error
exclamationToken?: ExclamationToken;
type?: TypeNode;
initializer?: Expression; // Optional initializer
}
Expand Down Expand Up @@ -860,6 +863,7 @@ namespace ts {
dotDotDotToken?: DotDotDotToken;
name: DeclarationName;
questionToken?: QuestionToken;
exclamationToken?: ExclamationToken;
type?: TypeNode;
initializer?: Expression;
}
Expand Down
6 changes: 5 additions & 1 deletion tests/baselines/reference/api/tsserverlibrary.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,7 @@ declare namespace ts {
}
type DotDotDotToken = Token<SyntaxKind.DotDotDotToken>;
type QuestionToken = Token<SyntaxKind.QuestionToken>;
type ExclamationToken = Token<SyntaxKind.ExclamationToken>;
type ColonToken = Token<SyntaxKind.ColonToken>;
type EqualsToken = Token<SyntaxKind.EqualsToken>;
type AsteriskToken = Token<SyntaxKind.AsteriskToken>;
Expand Down Expand Up @@ -537,6 +538,7 @@ declare namespace ts {
kind: SyntaxKind.VariableDeclaration;
parent?: VariableDeclarationList | CatchClause;
name: BindingName;
exclamationToken?: ExclamationToken;
type?: TypeNode;
initializer?: Expression;
}
Expand Down Expand Up @@ -571,8 +573,9 @@ declare namespace ts {
}
interface PropertyDeclaration extends ClassElement, JSDocContainer {
kind: SyntaxKind.PropertyDeclaration;
questionToken?: QuestionToken;
name: PropertyName;
questionToken?: QuestionToken;
exclamationToken?: ExclamationToken;
type?: TypeNode;
initializer?: Expression;
}
Expand Down Expand Up @@ -606,6 +609,7 @@ declare namespace ts {
dotDotDotToken?: DotDotDotToken;
name: DeclarationName;
questionToken?: QuestionToken;
exclamationToken?: ExclamationToken;
type?: TypeNode;
initializer?: Expression;
}
Expand Down
6 changes: 5 additions & 1 deletion tests/baselines/reference/api/typescript.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,7 @@ declare namespace ts {
}
type DotDotDotToken = Token<SyntaxKind.DotDotDotToken>;
type QuestionToken = Token<SyntaxKind.QuestionToken>;
type ExclamationToken = Token<SyntaxKind.ExclamationToken>;
type ColonToken = Token<SyntaxKind.ColonToken>;
type EqualsToken = Token<SyntaxKind.EqualsToken>;
type AsteriskToken = Token<SyntaxKind.AsteriskToken>;
Expand Down Expand Up @@ -537,6 +538,7 @@ declare namespace ts {
kind: SyntaxKind.VariableDeclaration;
parent?: VariableDeclarationList | CatchClause;
name: BindingName;
exclamationToken?: ExclamationToken;
type?: TypeNode;
initializer?: Expression;
}
Expand Down Expand Up @@ -571,8 +573,9 @@ declare namespace ts {
}
interface PropertyDeclaration extends ClassElement, JSDocContainer {
kind: SyntaxKind.PropertyDeclaration;
questionToken?: QuestionToken;
name: PropertyName;
questionToken?: QuestionToken;
exclamationToken?: ExclamationToken;
type?: TypeNode;
initializer?: Expression;
}
Expand Down Expand Up @@ -606,6 +609,7 @@ declare namespace ts {
dotDotDotToken?: DotDotDotToken;
name: DeclarationName;
questionToken?: QuestionToken;
exclamationToken?: ExclamationToken;
type?: TypeNode;
initializer?: Expression;
}
Expand Down
113 changes: 113 additions & 0 deletions tests/baselines/reference/definiteAssignmentAssertions.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
tests/cases/conformance/controlFlow/definiteAssignmentAssertions.ts(5,5): error TS2564: Property 'b' has no initializer and is not definitely assigned in the constructor.
tests/cases/conformance/controlFlow/definiteAssignmentAssertions.ts(20,6): error TS1255: A definite assignment assertion '!' is not permitted in this context.
tests/cases/conformance/controlFlow/definiteAssignmentAssertions.ts(21,6): error TS1255: A definite assignment assertion '!' is not permitted in this context.
tests/cases/conformance/controlFlow/definiteAssignmentAssertions.ts(22,13): error TS1255: A definite assignment assertion '!' is not permitted in this context.
tests/cases/conformance/controlFlow/definiteAssignmentAssertions.ts(28,6): error TS1255: A definite assignment assertion '!' is not permitted in this context.
tests/cases/conformance/controlFlow/definiteAssignmentAssertions.ts(34,15): error TS1255: A definite assignment assertion '!' is not permitted in this context.
tests/cases/conformance/controlFlow/definiteAssignmentAssertions.ts(68,10): error TS1255: A definite assignment assertion '!' is not permitted in this context.
tests/cases/conformance/controlFlow/definiteAssignmentAssertions.ts(69,10): error TS1255: A definite assignment assertion '!' is not permitted in this context.
tests/cases/conformance/controlFlow/definiteAssignmentAssertions.ts(70,10): error TS1255: A definite assignment assertion '!' is not permitted in this context.
tests/cases/conformance/controlFlow/definiteAssignmentAssertions.ts(75,15): error TS1255: A definite assignment assertion '!' is not permitted in this context.
tests/cases/conformance/controlFlow/definiteAssignmentAssertions.ts(76,15): error TS1255: A definite assignment assertion '!' is not permitted in this context.


==== tests/cases/conformance/controlFlow/definiteAssignmentAssertions.ts (11 errors) ====
// Suppress strict property initialization check

class C1 {
a!: number;
b: string; // Error
~
!!! error TS2564: Property 'b' has no initializer and is not definitely assigned in the constructor.
}

// Suppress definite assignment check in constructor

class C2 {
a!: number;
constructor() {
let x = this.a;
}
}

// Definite assignment assertion requires type annotation, no initializer, no static modifier

class C3 {
a! = 1;
~
!!! error TS1255: A definite assignment assertion '!' is not permitted in this context.
b!: number = 1;
~
!!! error TS1255: A definite assignment assertion '!' is not permitted in this context.
static c!: number;
~
!!! error TS1255: A definite assignment assertion '!' is not permitted in this context.
}

// Definite assignment assertion not permitted in ambient context

declare class C4 {
a!: number;
~
!!! error TS1255: A definite assignment assertion '!' is not permitted in this context.
}

// Definite assignment assertion not permitted on abstract property

abstract class C5 {
abstract a!: number;
~
!!! error TS1255: A definite assignment assertion '!' is not permitted in this context.
}

// Suppress definite assignment check for variable

function f1() {
let x!: number;
let y = x;
var a!: number;
var b = a;
}

function f2() {
let x!: string | number;
if (typeof x === "string") {
let s: string = x;
}
else {
let n: number = x;
}
}

function f3() {
let x!: number;
const g = () => {
x = 1;
}
g();
let y = x;
}

// Definite assignment assertion requires type annotation and no initializer

function f4() {
let a!;
~
!!! error TS1255: A definite assignment assertion '!' is not permitted in this context.
let b! = 1;
~
!!! error TS1255: A definite assignment assertion '!' is not permitted in this context.
let c!: number = 1;
~
!!! error TS1255: A definite assignment assertion '!' is not permitted in this context.
}

// Definite assignment assertion not permitted in ambient context

declare let v1!: number;
~
!!! error TS1255: A definite assignment assertion '!' is not permitted in this context.
declare var v2!: number;
~
!!! error TS1255: A definite assignment assertion '!' is not permitted in this context.

Loading

0 comments on commit 9abb72d

Please sign in to comment.