diff --git a/src/ast.ts b/src/ast.ts index 91738904a0..87a9f09ff3 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -901,6 +901,11 @@ export class NamedTypeNode extends TypeNode { let typeArguments = this.typeArguments; return typeArguments != null && typeArguments.length > 0; } + + /** Tests if this type is "null". */ + get isNull(): bool { + return this.name.identifier.text == "null"; + } } /** Represents a function type. */ diff --git a/src/parser.ts b/src/parser.ts index 981eb803e9..976307a49d 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -507,12 +507,12 @@ export class Parser extends DiagnosticEmitter { // '(' ... if (token == Token.OpenParen) { - // '(' FunctionSignature ')' '|' 'null'? - let isNullableSignature = tn.skip(Token.OpenParen); + // '(' FunctionSignature ')' + let isInnerParenthesized = tn.skip(Token.OpenParen); // FunctionSignature? let signature = this.tryParseFunctionType(tn); if (signature) { - if (isNullableSignature) { + if (isInnerParenthesized) { if (!tn.skip(Token.CloseParen)) { this.error( DiagnosticCode._0_expected, @@ -520,32 +520,16 @@ export class Parser extends DiagnosticEmitter { ); return null; } - if (!tn.skip(Token.Bar)) { - this.error( - DiagnosticCode._0_expected, - tn.range(), "|" - ); - return null; - } - if (!tn.skip(Token.Null)) { - this.error( - DiagnosticCode._0_expected, - tn.range(), "null" - ); - } - signature.isNullable = true; } - return signature; - } else if (isNullableSignature || this.tryParseSignatureIsSignature) { + type = signature; + } else if (isInnerParenthesized || this.tryParseSignatureIsSignature) { this.error( DiagnosticCode.Unexpected_token, tn.range() ); return null; - } - // Type (',' Type)* ')' - if (acceptParenthesized) { + } else if (acceptParenthesized) { let innerType = this.parseType(tn, false, suppressErrors); if (!innerType) return null; if (!tn.skip(Token.CloseParen)) { @@ -634,20 +618,29 @@ export class Parser extends DiagnosticEmitter { } return null; } - // ... | null + // ... | type while (tn.skip(Token.Bar)) { - if (tn.skip(Token.Null)) { - type.isNullable = true; - } else { - let notNullStart = tn.pos; - let notNull = this.parseType(tn, false, true); + let nextType = this.parseType(tn, false, true); + if (!nextType) return null; + let typeIsNull = type.kind == NodeKind.NamedType && (type).isNull; + let nextTypeIsNull = nextType.kind == NodeKind.NamedType && (nextType).isNull; + if (!typeIsNull && !nextTypeIsNull) { if (!suppressErrors) { this.error( - DiagnosticCode._0_expected, - notNull ? notNull.range : tn.range(notNullStart), "null" + DiagnosticCode.Not_implemented_0, nextType.range, "union types" ); } return null; + } else if (nextTypeIsNull) { + type.isNullable = true; + type.range.end = nextType.range.end; + } else if (typeIsNull) { + nextType.range.start = type.range.start; + nextType.isNullable = true; + type = nextType; + } else { + // `null | null` still `null` + type.range.end = nextType.range.end; } } // ... [][] @@ -672,8 +665,8 @@ export class Parser extends DiagnosticEmitter { } else { if (!suppressErrors) { this.error( - DiagnosticCode._0_expected, - tn.range(), "null" + DiagnosticCode.Not_implemented_0, + tn.range(), "union types" ); } return null; diff --git a/tests/parser/type-signature.ts b/tests/parser/type-signature.ts index 1e28ea4bf5..3e3dc2e49a 100644 --- a/tests/parser/type-signature.ts +++ b/tests/parser/type-signature.ts @@ -1,5 +1,9 @@ type foo = () => void; type foo = (() => void) | null; +type foo = null | (() => void); +type foo = (() => void)[]; +type foo = (() => void)[] | null; +type foo = null | (() => void)[]; type foo = (a: i32) => i32; type foo = (a?: i32) => i32; type foo = (this: AClass, a: i32) => i32; diff --git a/tests/parser/type-signature.ts.fixture.ts b/tests/parser/type-signature.ts.fixture.ts index 1e28ea4bf5..731c9cde40 100644 --- a/tests/parser/type-signature.ts.fixture.ts +++ b/tests/parser/type-signature.ts.fixture.ts @@ -1,5 +1,9 @@ type foo = () => void; type foo = (() => void) | null; +type foo = (() => void) | null; +type foo = Array<() => void>; +type foo = Array<() => void> | null; +type foo = Array<() => void> | null; type foo = (a: i32) => i32; type foo = (a?: i32) => i32; type foo = (this: AClass, a: i32) => i32; diff --git a/tests/parser/type.ts b/tests/parser/type.ts index 7099da72d8..609e12c9a3 100644 --- a/tests/parser/type.ts +++ b/tests/parser/type.ts @@ -16,3 +16,7 @@ export type T7 = Array; export type T8 = Map>; export type T9 = Array<() => T9>; export type T10 = T6; + +export type T11 = T1 | null +export type T12 = null | T1 + diff --git a/tests/parser/type.ts.fixture.ts b/tests/parser/type.ts.fixture.ts index c6814cac17..01f7734e20 100644 --- a/tests/parser/type.ts.fixture.ts +++ b/tests/parser/type.ts.fixture.ts @@ -3,6 +3,8 @@ type int32_t = i32; export type uint64_t = u64; export type T1 = int32_t; export type T2 = int32_t; +export type T11 = T1 | null; +export type T12 = T1 | null; // ERROR 2456: "Type alias 'T3' circularly references itself." in type.ts(11,23+4) // ERROR 100: "Not implemented: Recursion in type aliases" in type.ts(12,29+3) // ERROR 100: "Not implemented: Recursion in type aliases" in type.ts(13,24+2)