diff --git a/src/common.ts b/src/common.ts index 638e3cba..af24dd09 100644 --- a/src/common.ts +++ b/src/common.ts @@ -36,44 +36,46 @@ export enum CommonFlags { GET = 1 << 11, /** Has a `set` modifier. */ SET = 1 << 12, + /** Has a definite assignment assertion `!` as in `x!: i32;`. */ + DEFINITE_ASSIGNMENT = 1 << 13, // Extended modifiers usually derived from basic modifiers /** Is ambient, that is either declared or nested in a declared element. */ - AMBIENT = 1 << 13, + AMBIENT = 1 << 14, /** Is generic. */ - GENERIC = 1 << 14, + GENERIC = 1 << 15, /** Is part of a generic context. */ - GENERIC_CONTEXT = 1 << 15, + GENERIC_CONTEXT = 1 << 16, /** Is an instance member. */ - INSTANCE = 1 << 16, + INSTANCE = 1 << 17, /** Is a constructor. */ - CONSTRUCTOR = 1 << 17, + CONSTRUCTOR = 1 << 18, /** Is an arrow function. */ - ARROW = 1 << 18, + ARROW = 1 << 19, /** Is a module export. */ - MODULE_EXPORT = 1 << 19, + MODULE_EXPORT = 1 << 20, /** Is a module import. */ - MODULE_IMPORT = 1 << 20, + MODULE_IMPORT = 1 << 21, // Compilation states /** Is compiled. */ - COMPILED = 1 << 21, + COMPILED = 1 << 22, /** Has a constant value and is therefore inlined. */ - INLINED = 1 << 22, + INLINED = 1 << 23, /** Is scoped. */ - SCOPED = 1 << 23, + SCOPED = 1 << 24, /** Is a trampoline. */ - TRAMPOLINE = 1 << 24, + TRAMPOLINE = 1 << 25, /** Is a virtual method. */ - VIRTUAL = 1 << 25, + VIRTUAL = 1 << 26, /** Is the main function. */ - MAIN = 1 << 26, + MAIN = 1 << 27, // Other - QUOTED = 1 << 27 + QUOTED = 1 << 28 } /** Path delimiter inserted between file system levels. */ diff --git a/src/diagnosticMessages.generated.ts b/src/diagnosticMessages.generated.ts index fd1f65eb..94af4a26 100644 --- a/src/diagnosticMessages.generated.ts +++ b/src/diagnosticMessages.generated.ts @@ -29,6 +29,7 @@ export enum DiagnosticCode { Constructor_of_class_0_must_not_require_any_arguments = 216, Function_0_cannot_be_inlined_into_itself = 217, Cannot_access_method_0_without_calling_it_as_it_requires_this_to_be_set = 218, + Optional_properties_are_not_supported = 219, Unterminated_string_literal = 1002, Identifier_expected = 1003, _0_expected = 1005, @@ -81,6 +82,7 @@ export enum DiagnosticCode { Decorators_are_not_valid_here = 1206, _abstract_modifier_can_only_appear_on_a_class_method_or_property_declaration = 1242, Method_0_cannot_have_an_implementation_because_it_is_marked_abstract = 1245, + A_definite_assignment_assertion_is_not_permitted_in_this_context = 1255, A_class_may_only_extend_another_class = 1311, A_parameter_property_cannot_be_declared_using_a_rest_parameter = 1317, Duplicate_identifier_0 = 2300, @@ -149,6 +151,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string { case 216: return "Constructor of class '{0}' must not require any arguments."; case 217: return "Function '{0}' cannot be inlined into itself."; case 218: return "Cannot access method '{0}' without calling it as it requires 'this' to be set."; + case 219: return "Optional properties are not supported."; case 1002: return "Unterminated string literal."; case 1003: return "Identifier expected."; case 1005: return "'{0}' expected."; @@ -201,6 +204,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string { case 1206: return "Decorators are not valid here."; case 1242: return "'abstract' modifier can only appear on a class, method, or property declaration."; case 1245: return "Method '{0}' cannot have an implementation because it is marked abstract."; + case 1255: return "A definite assignment assertion '!' is not permitted in this context."; case 1311: return "A class may only extend another class."; case 1317: return "A parameter property cannot be declared using a rest parameter."; case 2300: return "Duplicate identifier '{0}'."; diff --git a/src/diagnosticMessages.json b/src/diagnosticMessages.json index 647a172e..b9607098 100644 --- a/src/diagnosticMessages.json +++ b/src/diagnosticMessages.json @@ -21,6 +21,7 @@ "Constructor of class '{0}' must not require any arguments.": 216, "Function '{0}' cannot be inlined into itself.": 217, "Cannot access method '{0}' without calling it as it requires 'this' to be set.": 218, + "Optional properties are not supported.": 219, "Unterminated string literal.": 1002, "Identifier expected.": 1003, @@ -74,6 +75,7 @@ "Decorators are not valid here.": 1206, "'abstract' modifier can only appear on a class, method, or property declaration.": 1242, "Method '{0}' cannot have an implementation because it is marked abstract.": 1245, + "A definite assignment assertion '!' is not permitted in this context.": 1255, "A class may only extend another class.": 1311, "A parameter property cannot be declared using a rest parameter.": 1317, diff --git a/src/extra/ast.ts b/src/extra/ast.ts index b8cae53a..2e005526 100644 --- a/src/extra/ast.ts +++ b/src/extra/ast.ts @@ -990,6 +990,9 @@ export class ASTBuilder { this.serializeAccessModifiers(node); this.visitIdentifierExpression(node.name); var sb = this.sb; + if (node.flags & CommonFlags.DEFINITE_ASSIGNMENT) { + sb.push("!"); + } var type = node.type; if (type) { sb.push(": "); @@ -1376,6 +1379,9 @@ export class ASTBuilder { this.visitIdentifierExpression(node.name); var type = node.type; var sb = this.sb; + if (node.flags & CommonFlags.DEFINITE_ASSIGNMENT) { + sb.push("!"); + } if (type) { sb.push(": "); this.visitTypeNode(type); diff --git a/src/parser.ts b/src/parser.ts index 5c12524a..3bda5032 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -769,6 +769,9 @@ export class Parser extends DiagnosticEmitter { } var identifier = Node.createIdentifierExpression(tn.readIdentifier(), tn.range()); var flags = parentFlags; + if (tn.skip(Token.EXCLAMATION)) { + flags |= CommonFlags.DEFINITE_ASSIGNMENT; + } var type: CommonTypeNode | null = null; if (tn.skip(Token.COLON)) { @@ -800,13 +803,19 @@ export class Parser extends DiagnosticEmitter { ); // recoverable } } + var range = Range.join(identifier.range, tn.range()); + if ((flags & CommonFlags.DEFINITE_ASSIGNMENT) && initializer) { + this.error( + DiagnosticCode.A_definite_assignment_assertion_is_not_permitted_in_this_context, + range); + } return Node.createVariableDeclaration( identifier, type, initializer, parentDecorators, flags, - Range.join(identifier.range, tn.range()) + range ); } @@ -1926,6 +1935,15 @@ export class Parser extends DiagnosticEmitter { } let type: CommonTypeNode | null = null; + if (tn.skip(Token.QUESTION)) { + this.error( + DiagnosticCode.Optional_properties_are_not_supported, + tn.range(startPos, tn.pos) + ); + } + if (tn.skip(Token.EXCLAMATION)) { + flags |= CommonFlags.DEFINITE_ASSIGNMENT; + } if (tn.skip(Token.COLON)) { type = this.parseType(tn); if (!type) return null; @@ -1940,13 +1958,20 @@ export class Parser extends DiagnosticEmitter { initializer = this.parseExpression(tn); if (!initializer) return null; } + let range = tn.range(startPos, tn.pos); + if ((flags & CommonFlags.DEFINITE_ASSIGNMENT) && ((flags & CommonFlags.STATIC) || isInterface || initializer)) { + this.error( + DiagnosticCode.A_definite_assignment_assertion_is_not_permitted_in_this_context, + range + ); + } let retField = Node.createFieldDeclaration( name, type, initializer, decorators, flags, - tn.range(startPos, tn.pos) + range ); tn.skip(Token.SEMICOLON); return retField; diff --git a/tests/parser/definite-assignment-assertion.ts b/tests/parser/definite-assignment-assertion.ts new file mode 100644 index 00000000..676f66f2 --- /dev/null +++ b/tests/parser/definite-assignment-assertion.ts @@ -0,0 +1,9 @@ +class C { + x!: i32; + x!: i32 = 0; + static x!: i32; +} +function f(): void { + let x!: i32; + let x!: i32 = 0; +} diff --git a/tests/parser/definite-assignment-assertion.ts.fixture.ts b/tests/parser/definite-assignment-assertion.ts.fixture.ts new file mode 100644 index 00000000..e90f5786 --- /dev/null +++ b/tests/parser/definite-assignment-assertion.ts.fixture.ts @@ -0,0 +1,12 @@ +class C { + x!: i32; + x!: i32 = 0; + static x!: i32; +} +function f(): void { + let x!: i32; + let x!: i32 = 0; +} +// ERROR 1255: "A definite assignment assertion '!' is not permitted in this context." in definite-assignment-assertion.ts:3:10 +// ERROR 1255: "A definite assignment assertion '!' is not permitted in this context." in definite-assignment-assertion.ts:4:14 +// ERROR 1255: "A definite assignment assertion '!' is not permitted in this context." in definite-assignment-assertion.ts:8:6 diff --git a/tests/parser/optional-property.ts b/tests/parser/optional-property.ts new file mode 100644 index 00000000..899c00c6 --- /dev/null +++ b/tests/parser/optional-property.ts @@ -0,0 +1,3 @@ +class C { + x?: i32; +} diff --git a/tests/parser/optional-property.ts.fixture.ts b/tests/parser/optional-property.ts.fixture.ts new file mode 100644 index 00000000..156a4d03 --- /dev/null +++ b/tests/parser/optional-property.ts.fixture.ts @@ -0,0 +1,4 @@ +class C { + x: i32; +} +// ERROR 219: "Optional properties are not supported." in optional-property.ts:2:9