Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Definite assignment assertions #20166

Merged
merged 7 commits into from
Nov 21, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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