From f3e46097e01509acbbc9da2a229381445af798aa Mon Sep 17 00:00:00 2001 From: Congcong Cai Date: Tue, 6 Sep 2022 14:01:27 +0800 Subject: [PATCH 1/5] fix|parser: support `null | string` type --- src/ast.ts | 8 ++++++++ src/parser.ts | 31 +++++++++++++++++++++---------- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/src/ast.ts b/src/ast.ts index bc070e7f0b..c71f04849f 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -830,6 +830,14 @@ export abstract class TypeNode extends Node { super(kind, range); } + isNullType(): bool { + if (this.kind == NodeKind.NAMEDTYPE) { + let namedTypeNode = changetype(this); // TS + return namedTypeNode.name.identifier.text == "null"; + } + return false; + } + /** Tests if this type has a generic component matching one of the given type parameters. */ hasGenericComponent(typeParameterNodes: TypeParameterNode[]): bool { if (this.kind == NodeKind.NAMEDTYPE) { diff --git a/src/parser.ts b/src/parser.ts index 314d3a2cdf..9852b2bb55 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -634,20 +634,31 @@ 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 == null) { + return null; + } + let nextTypeIsNull = nextType.isNullType(); + let typeIsNull = type.isNullType(); + 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 +683,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; From c1dd005477453de28b8e2c1cbe07df0f5904c8f5 Mon Sep 17 00:00:00 2001 From: Congcong Cai Date: Tue, 6 Sep 2022 14:21:53 +0800 Subject: [PATCH 2/5] fix|parser: function signature with `| null` --- src/parser.ts | 27 ++++++--------------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/src/parser.ts b/src/parser.ts index 9852b2bb55..80bfb2b400 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 isOpenParen = tn.skip(Token.OPENPAREN); // FunctionSignature? let signature = this.tryParseFunctionType(tn); if (signature) { - if (isNullableSignature) { + if (isOpenParen) { if (!tn.skip(Token.CLOSEPAREN)) { this.error( DiagnosticCode._0_expected, @@ -520,32 +520,17 @@ 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 (isOpenParen || 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)) { From 0d13241a4b4a268902161def6b5f2de890b2dd8a Mon Sep 17 00:00:00 2001 From: Congcong Cai Date: Tue, 6 Sep 2022 14:22:28 +0800 Subject: [PATCH 3/5] add testcase --- tests/parser/type-signature.ts | 4 ++++ tests/parser/type-signature.ts.fixture.ts | 4 ++++ tests/parser/type.ts | 4 ++++ tests/parser/type.ts.fixture.ts | 2 ++ 4 files changed, 14 insertions(+) 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) From bb4978ae507f738039d5c20e0aa24dee149c2776 Mon Sep 17 00:00:00 2001 From: Congcong Cai Date: Wed, 7 Sep 2022 00:31:20 +0800 Subject: [PATCH 4/5] refactory --- src/ast.ts | 8 -------- src/parser.ts | 4 ++-- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/ast.ts b/src/ast.ts index c71f04849f..bc070e7f0b 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -830,14 +830,6 @@ export abstract class TypeNode extends Node { super(kind, range); } - isNullType(): bool { - if (this.kind == NodeKind.NAMEDTYPE) { - let namedTypeNode = changetype(this); // TS - return namedTypeNode.name.identifier.text == "null"; - } - return false; - } - /** Tests if this type has a generic component matching one of the given type parameters. */ hasGenericComponent(typeParameterNodes: TypeParameterNode[]): bool { if (this.kind == NodeKind.NAMEDTYPE) { diff --git a/src/parser.ts b/src/parser.ts index 80bfb2b400..f58cb74f7c 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -625,8 +625,8 @@ export class Parser extends DiagnosticEmitter { if (nextType == null) { return null; } - let nextTypeIsNull = nextType.isNullType(); - let typeIsNull = type.isNullType(); + let nextTypeIsNull = nextType.kind == NodeKind.NAMEDTYPE && (nextType).name.identifier.text == "null"; + let typeIsNull = type.kind == NodeKind.NAMEDTYPE && (type).name.identifier.text == "null"; if (!typeIsNull && !nextTypeIsNull) { if (!suppressErrors) { this.error( From 6a1f03f8e4fab9714259643eaaa9926c17bf4a0b Mon Sep 17 00:00:00 2001 From: dcode Date: Tue, 17 Jan 2023 12:29:50 +0100 Subject: [PATCH 5/5] minor --- src/ast.ts | 5 +++++ src/parser.ts | 17 +++++++---------- 2 files changed, 12 insertions(+), 10 deletions(-) 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 2bf8bc6328..976307a49d 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -508,11 +508,11 @@ export class Parser extends DiagnosticEmitter { if (token == Token.OpenParen) { // '(' FunctionSignature ')' - let isOpenParen = tn.skip(Token.OpenParen); + let isInnerParenthesized = tn.skip(Token.OpenParen); // FunctionSignature? let signature = this.tryParseFunctionType(tn); if (signature) { - if (isOpenParen) { + if (isInnerParenthesized) { if (!tn.skip(Token.CloseParen)) { this.error( DiagnosticCode._0_expected, @@ -522,15 +522,14 @@ export class Parser extends DiagnosticEmitter { } } type = signature; - } else if (isOpenParen || this.tryParseSignatureIsSignature) { + } else if (isInnerParenthesized || this.tryParseSignatureIsSignature) { this.error( DiagnosticCode.Unexpected_token, tn.range() ); return null; - } // Type (',' Type)* ')' - else if (acceptParenthesized) { + } else if (acceptParenthesized) { let innerType = this.parseType(tn, false, suppressErrors); if (!innerType) return null; if (!tn.skip(Token.CloseParen)) { @@ -622,11 +621,9 @@ export class Parser extends DiagnosticEmitter { // ... | type while (tn.skip(Token.Bar)) { let nextType = this.parseType(tn, false, true); - if (nextType == null) { - return null; - } - let nextTypeIsNull = nextType.kind == NodeKind.NamedType && (nextType).name.identifier.text == "null"; - let typeIsNull = type.kind == NodeKind.NamedType && (type).name.identifier.text == "null"; + 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(