diff --git a/NOTICE b/NOTICE index 148bc0ca87..da2cb563d4 100644 --- a/NOTICE +++ b/NOTICE @@ -51,7 +51,7 @@ under the licensing terms detailed in LICENSE: * Adrien Zinger * Ruixiang Chen * Daniel Salvadori -* Jairus Tanaka +* Jairus Tanaka * CountBleck * Abdul Rauf * Bach Le diff --git a/src/ast.ts b/src/ast.ts index 01d8e9a421..da649de549 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -50,6 +50,7 @@ export const enum NodeKind { // types NamedType, FunctionType, + TupleType, TypeName, TypeParameter, Parameter, @@ -161,6 +162,15 @@ export abstract class Node { return new FunctionTypeNode(parameters, returnType, explicitThisType, isNullable, range); } + static createTupleType( + elements: TypeNode[], + elementNames: (IdentifierExpression | null)[] | null, + isNullable: bool, + range: Range + ): TupleTypeNode { + return new TupleTypeNode(elements, elementNames, isNullable, range); + } + static createOmittedType( range: Range ): NamedTypeNode { @@ -862,6 +872,12 @@ export abstract class TypeNode extends Node { if (functionTypeNode.returnType.hasGenericComponent(typeParameterNodes)) return true; let explicitThisType = functionTypeNode.explicitThisType; if (explicitThisType && explicitThisType.hasGenericComponent(typeParameterNodes)) return true; + } else if (this.kind == NodeKind.TupleType) { + let tupleTypeNode = changetype(this); + let elements = tupleTypeNode.elements; + for (let i = 0, k = elements.length; i < k; ++i) { + if (elements[i].hasGenericComponent(typeParameterNodes)) return true; + } } else { assert(false); } @@ -928,6 +944,22 @@ export class FunctionTypeNode extends TypeNode { } } +/** Represents a tuple type. */ +export class TupleTypeNode extends TypeNode { + constructor( + /** Tuple elements. */ + public elements: TypeNode[], + /** Tuple element names, if any. */ + public elementNames: (IdentifierExpression | null)[] | null, + /** Whether nullable or not. */ + isNullable: bool, + /** Source range. */ + range: Range + ) { + super(NodeKind.TupleType, isNullable, range); + } +} + /** Represents a type parameter. */ export class TypeParameterNode extends Node { constructor( diff --git a/src/extra/ast.ts b/src/extra/ast.ts index ebe9217f90..699b2e0c71 100644 --- a/src/extra/ast.ts +++ b/src/extra/ast.ts @@ -15,6 +15,7 @@ import { TypeNode, NamedTypeNode, FunctionTypeNode, + TupleTypeNode, TypeName, TypeParameterNode, @@ -134,6 +135,10 @@ export class ASTBuilder { this.visitFunctionTypeNode(node); break; } + case NodeKind.TupleType: { + this.visitTupleTypeNode(node); + break; + } case NodeKind.TypeParameter: { this.visitTypeParameter(node); break; @@ -387,6 +392,10 @@ export class ASTBuilder { this.visitFunctionTypeNode(node); break; } + case NodeKind.TupleType: { + this.visitTupleTypeNode(node); + break; + } default: assert(false); } } @@ -450,6 +459,33 @@ export class ASTBuilder { if (isNullable) sb.push(") | null"); } + visitTupleTypeNode(node: TupleTypeNode): void { + let sb = this.sb; + sb.push("["); + let elements = node.elements; + let elementNames = node.elementNames; + let numElements = elements.length; + if (numElements) { + let name = elementNames ? elementNames[0] : null; + if (name) { + this.visitIdentifierExpression(name); + sb.push(": "); + } + this.visitTypeNode(elements[0]); + for (let i = 1; i < numElements; ++i) { + sb.push(", "); + name = elementNames ? elementNames[i] : null; + if (name) { + this.visitIdentifierExpression(name); + sb.push(": "); + } + this.visitTypeNode(elements[i]); + } + } + sb.push("]"); + if (node.isNullable) sb.push(" | null"); + } + visitTypeParameter(node: TypeParameterNode): void { this.visitIdentifierExpression(node.name); let extendsType = node.extendsType; diff --git a/src/parser.ts b/src/parser.ts index 7c69843973..ef2f17ae9c 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -9,6 +9,7 @@ import { CommonFlags, + Feature, LIBRARY_PREFIX, PATH_DELIMITER } from "./common"; @@ -42,6 +43,7 @@ import { TypeName, NamedTypeNode, FunctionTypeNode, + TupleTypeNode, ArrowKind, Expression, @@ -90,6 +92,7 @@ import { mangleInternalPath } from "./ast"; +import { Options } from "./compiler"; /** Represents a dependee. */ class Dependee { @@ -118,7 +121,8 @@ export class Parser extends DiagnosticEmitter { sources: Source[]; /** Current overridden module name. */ currentModuleName: string | null = null; - + /** Temporary variable so I can disable parsing tuples if multi-variable is disabled */ + options: Options | null = null; /** Constructs a new parser. */ constructor( diagnostics: DiagnosticMessage[] | null = null, @@ -509,8 +513,19 @@ export class Parser extends DiagnosticEmitter { let type: TypeNode; + // 'readonly' Type + if (token == Token.Readonly) { + let innerType = this.parseType(tn, acceptParenthesized, suppressErrors); + if (!innerType) return null; + type = Node.createNamedType( + Node.createSimpleTypeName("Readonly", tn.range(startPos, tn.pos)), + [ innerType ], + false, + tn.range(startPos, tn.pos) + ); + // '(' ... - if (token == Token.OpenParen) { + } else if (token == Token.OpenParen) { // '(' FunctionSignature ')' let isInnerParenthesized = tn.skip(Token.OpenParen); @@ -563,6 +578,42 @@ export class Parser extends DiagnosticEmitter { return null; } + // '[' Type (',' Type)* ']' + } else if (this.options && this.options!.hasFeature(Feature.MultiValue) && token == Token.OpenBracket) { + let elements: TypeNode[] = []; + let elementNames: (IdentifierExpression | null)[] = []; + let hasElementNames = false; + if (!tn.skip(Token.CloseBracket)) { + do { + let elementName: IdentifierExpression | null = null; + let state = tn.mark(); + if (tn.skip(Token.Identifier)) { + let name = tn.readIdentifier(); + let nameRange = tn.range(); + if (tn.skip(Token.Colon)) { + elementName = Node.createIdentifierExpression(name, nameRange); + hasElementNames = true; + } else { + tn.reset(state); + } + } + let element = this.parseType(tn, true, suppressErrors); + if (!element) return null; + elements.push(element); + elementNames.push(elementName); + } while (tn.skip(Token.Comma)); + if (!tn.skip(Token.CloseBracket)) { + if (!suppressErrors) { + this.error( + DiagnosticCode._0_expected, + tn.range(tn.pos), "]" + ); + } + return null; + } + } + type = Node.createTupleType(elements, hasElementNames ? elementNames : null, false, tn.range(startPos, tn.pos)); + // 'void' } else if (token == Token.Void) { type = Node.createNamedType( @@ -4581,6 +4632,13 @@ function isCircularTypeAlias(name: string, type: TypeNode): bool { } break; } + case NodeKind.TupleType: { + let elements = (type).elements; + for (let i = 0, k = elements.length; i < k; i++) { + if (isCircularTypeAlias(name, elements[i])) return true; + } + break; + } default: assert(false); } return false; diff --git a/src/program.ts b/src/program.ts index e5e27ebc59..49fa2f3460 100644 --- a/src/program.ts +++ b/src/program.ts @@ -442,6 +442,9 @@ export class Program extends DiagnosticEmitter { let nativeFile = new File(this, Source.native); this.nativeFile = nativeFile; this.filesByName.set(nativeFile.internalName, nativeFile); + + // temporary fix + this.parser.options = this.options; } /** Module instance. */ diff --git a/src/resolver.ts b/src/resolver.ts index 2a4df6c3e7..2b3b4d49e8 100644 --- a/src/resolver.ts +++ b/src/resolver.ts @@ -45,6 +45,7 @@ import { import { FunctionTypeNode, + TupleTypeNode, ParameterKind, TypeNode, NodeKind, @@ -171,6 +172,10 @@ export class Resolver extends DiagnosticEmitter { resolved = this.resolveFunctionType(node, flow, ctxElement, ctxTypes, reportMode); break; } + case NodeKind.TupleType: { + resolved = this.resolveTupleType(node, flow, ctxElement, ctxTypes, reportMode); + break; + } default: assert(false); } node.currentlyResolving = false; @@ -452,6 +457,32 @@ export class Resolver extends DiagnosticEmitter { return node.isNullable ? signature.type.asNullable() : signature.type; } + /** Resolves a {@link TupleTypeNode}. */ + private resolveTupleType( + /** The type to resolve. */ + node: TupleTypeNode, + /** The flow */ + flow: Flow | null, + /** Contextual element. */ + ctxElement: Element, + /** Contextual types, i.e. `T`. */ + ctxTypes: Map | null = null, + /** How to proceed with eventual diagnostics. */ + reportMode: ReportMode = ReportMode.Report + ): Type | null { + let elements = node.elements; + for (let i = 0, k = elements.length; i < k; ++i) { + if (!this.resolveType(elements[i], flow, ctxElement, ctxTypes, reportMode)) return null; + } + if (reportMode == ReportMode.Report) { + this.error( + DiagnosticCode.Not_implemented_0, + node.range, "Tuple types" + ); + } + return null; + } + private resolveBuiltinNotNullableType( /** The type to resolve. */ node: NamedTypeNode, diff --git a/tests/compiler/tuple-circular.json b/tests/compiler/tuple-circular.json new file mode 100644 index 0000000000..d196b422ed --- /dev/null +++ b/tests/compiler/tuple-circular.json @@ -0,0 +1,9 @@ +{ + "asc_flags": [ + "--enable", "multi-value" + ], + "stderr": [ + "TS2456: Type alias 'Loop' circularly references itself.", + "1 parse error(s)" + ] +} diff --git a/tests/compiler/tuple-circular.ts b/tests/compiler/tuple-circular.ts new file mode 100644 index 0000000000..dd016d7679 --- /dev/null +++ b/tests/compiler/tuple-circular.ts @@ -0,0 +1 @@ +type Loop = [Loop, i32]; diff --git a/tests/compiler/tuple-disabled.json b/tests/compiler/tuple-disabled.json new file mode 100644 index 0000000000..b4e8e8ffe9 --- /dev/null +++ b/tests/compiler/tuple-disabled.json @@ -0,0 +1,9 @@ +{ + "asc_flags": [ + "--disable", "multi-value" + ], + "stderr": [ + "TS1110: Type expected.", + "3 parse error(s)" + ] +} diff --git a/tests/compiler/tuple-disabled.ts b/tests/compiler/tuple-disabled.ts new file mode 100644 index 0000000000..5c93a3e424 --- /dev/null +++ b/tests/compiler/tuple-disabled.ts @@ -0,0 +1,3 @@ +export function tupleDisabled(x: [left: i32, right: i32]): void {} +export type tupleTypeDisabled1 = [i32, i32]; +export type tupleTypeDisabled2 = []; diff --git a/tests/compiler/tuple-errors.json b/tests/compiler/tuple-errors.json new file mode 100644 index 0000000000..b4c0f65ed3 --- /dev/null +++ b/tests/compiler/tuple-errors.json @@ -0,0 +1,9 @@ +{ + "asc_flags": [ + "--enable", "multi-value" + ], + "stderr": [ + "AS100: Not implemented: Tuple types", + "20 compile error(s)" + ] +} diff --git a/tests/compiler/tuple-errors.ts b/tests/compiler/tuple-errors.ts new file mode 100644 index 0000000000..2bab1e6bb2 --- /dev/null +++ b/tests/compiler/tuple-errors.ts @@ -0,0 +1,65 @@ +export type TupleTypeUnimplemented1 = []; +export type TupleTypeUnimplemented2 = [i32]; +export type TupleTypeUnimplemented3 = [i32, []]; +export type TupleTypeUnimplemented4 = [i32, TupleTypeUnimplemented1]; +export type TupleTypeUnimplemented5 = [string, i32]; +export type TupleTypeUnimplemented6 = [i32[], [i32]]; +export type TupleTypeUnimplemented7 = [i32, i32]; +export type TupleTypeUnimplemented8 = [x: i32, y: i32]; + +export function TupleParamUnimplemented1(x: []): void { } +export function TupleParamUnimplemented2(x: [i32]): void { } +export function TupleParamUnimplemented3(x: [i32, []]): void { } +export function TupleParamUnimplemented4(x: [i32, TupleTypeUnimplemented1]): void { } +export function TupleParamUnimplemented5(x: [string, i32]): void { } +export function TupleParamUnimplemented6(x: [i32[], [i32]]): void { } +export function TupleParamUnimplemented7(x: [i32, i32]): void { } +export function TupleParamUnimplemented8(x: [left: i32, right: i32]): void { } + +export function TupleReturnUnimplemented1(): [] { + return []; +} +export function TupleReturnUnimplemented2(): [i32] { + return [0]; +} +export function TupleReturnUnimplemented3(): [i32, []] { + return [0, []]; +} +export function TupleReturnUnimplemented4(): [i32, TupleTypeUnimplemented1] { + return [0, []]; +} +export function TupleReturnUnimplemented5(): [string, i32] { + return ["foo", 0]; +} +export function TupleReturnUnimplemented6(): [i32[], [i32]] { + return [new Array(), [0]]; +} +export function TupleReturnUnimplemented7(): [i32, i32] { + return [0, 1]; +} +export function TupleReturnUnimplemented8(): [first: i32, second: i32] { + return [0, 1]; +} + +type Box = [T, i32]; + +export function TupleGeneric1(x: Box): Box { + return x; +} +export function TupleGeneric2(x: [i32, T]): [i32, T] { + return x; +} + +export function TupleNullable1(x: [i32, i32] | null): [i32, i32] | null { + return x; +} +export function TupleNullable2(x: [] | null): [] | null { + return x; +} + +export function TupleTypeMismatch1(x: [i32, f32]): [f32, i32] { + return x; +} +export function TupleTypeMismatch2(x: [f64, f32]): [f32, f64] { + return x; +} diff --git a/tests/compiler/tuple-type.json b/tests/compiler/tuple-type.json new file mode 100644 index 0000000000..8a83de2d20 --- /dev/null +++ b/tests/compiler/tuple-type.json @@ -0,0 +1,7 @@ +{ + "asc_flags": [ + "--enable", "multi-value" + ], + "stderr": [ + ] +} diff --git a/tests/compiler/tuple-type.ts b/tests/compiler/tuple-type.ts new file mode 100644 index 0000000000..8cf62aeb86 --- /dev/null +++ b/tests/compiler/tuple-type.ts @@ -0,0 +1 @@ +// nothing yet just getting the parser working first diff --git a/tests/parser.js b/tests/parser.js index ec3d89f0d9..687059529a 100644 --- a/tests/parser.js +++ b/tests/parser.js @@ -6,7 +6,7 @@ import { globSync } from "glob"; import { diff } from "../util/text.js"; import { stdoutColors } from "../util/terminal.js"; import * as optionsUtil from "../util/options.js"; -import { Program, Options, ASTBuilder } from "../dist/assemblyscript.js"; +import { Program, Options, ASTBuilder, Feature } from "../dist/assemblyscript.js"; const dirname = path.dirname(fileURLToPath(import.meta.url)); @@ -54,6 +54,11 @@ if (argv.length) { } let failures = 0; +const parserTestFeatures = new Map([ + ["tuple.ts", [Feature.MultiValue]], + ["tuple-more.ts", [Feature.MultiValue]], + ["tuple-errors.ts", [Feature.MultiValue]] +]); for (const filename of tests) { if (filename.charAt(0) == "_" || filename.endsWith(".fixture.ts")) continue; @@ -61,9 +66,16 @@ for (const filename of tests) { console.log(stdoutColors.white("Testing parser/" + filename)); let failed = false; - const program = new Program(new Options()); - const parser = program.parser; + const options = new Options(); const sourceText = fs.readFileSync(basedir + "/" + filename, { encoding: "utf8" }).replace(/\r?\n/g, "\n"); + const features = parserTestFeatures.get(filename); + if (features) { + for (const feature of features) { + options.setFeature(feature); + } + } + const program = new Program(options); + const parser = program.parser; parser.parseFile(sourceText, filename, true); const serializedSourceText = ASTBuilder.build(program.sources[0]); const actual = serializedSourceText + parser.diagnostics.map(diagnostic => "// " + diagnostic +"\n").join(""); diff --git a/tests/parser/tuple-errors.ts b/tests/parser/tuple-errors.ts new file mode 100644 index 0000000000..ecfed92541 --- /dev/null +++ b/tests/parser/tuple-errors.ts @@ -0,0 +1,3 @@ +export type Loop = [Loop, i32]; +export type BadTuple = [i32, ]; +export function badReadonly(x: readonly): void {} diff --git a/tests/parser/tuple-errors.ts.fixture.ts b/tests/parser/tuple-errors.ts.fixture.ts new file mode 100644 index 0000000000..d4ae1a5762 --- /dev/null +++ b/tests/parser/tuple-errors.ts.fixture.ts @@ -0,0 +1,3 @@ +// ERROR 2456: "Type alias 'Loop' circularly references itself." in tuple-errors.ts(1,13+4) +// ERROR 1110: "Type expected." in tuple-errors.ts(2,30+1) +// ERROR 1110: "Type expected." in tuple-errors.ts(3,40+1) diff --git a/tests/parser/tuple.ts b/tests/parser/tuple.ts new file mode 100644 index 0000000000..0cb29747f4 --- /dev/null +++ b/tests/parser/tuple.ts @@ -0,0 +1,25 @@ +function tuple1(): [i32, i32] { return [1, 2]; } +function tuple2(): [i32, [i32, i32]] { return [1, [2, 3]]; } +function tuple3(): [i32, string] { return [1, "a"]; } +function tuple4(): [Array, i32[]] { return [new Array(), [1, 2]]; } +function tuple5(): [i32] { return [1]; } +function tuple6(): [[i32[]]] { return [[[1, 2]]]; } +function tuple7(): [x: i32, y: i32] { return [1, 2]; } +function tuple8(): [head: i32, tail: [lo: i32, hi: i32]] { return [1, [2, 3]]; } + +function func1(a: i32, b: i32): [i32, i32] { return [a, b]; } +function func2(x: [i32, i32]): [i32, i32] { return x; } +function func3(x: [i32, [i32, i32]], y: i32): [i32, [i32, i32]] { return x; } +function func4(x: readonly [i32, string]): [void] { return [void(0)]; } +function func5(x: readonly [Array, i32[]]): readonly [i32] { return [x[1].length]; } +function func6(x: [i32, i32] | null): [i32, i32] | null { return x; } +function func7(x: readonly [[i32[]], [string]]): readonly [[i32[]], [string]] { return x; } +function func8(x: [left: i32, right: i32]): [first: i32, second: i32] { return x; } + +type type1 = [i32, i32]; +type type2 = [i32, [i32, i32]]; +type type3 = readonly [i32, string]; +type type4 = [i32, i32] | null; +type type5 = [[i32, i32], [i32, i32]]; +type type6 = [Array, T[], T]; +type type7 = [start: i32, end: [lo: i32, hi: i32]]; diff --git a/tests/parser/tuple.ts.fixture.ts b/tests/parser/tuple.ts.fixture.ts new file mode 100644 index 0000000000..d385f6b614 --- /dev/null +++ b/tests/parser/tuple.ts.fixture.ts @@ -0,0 +1,55 @@ +function tuple1(): [i32, i32] { + return [1, 2]; +} +function tuple2(): [i32, [i32, i32]] { + return [1, [2, 3]]; +} +function tuple3(): [i32, string] { + return [1, "a"]; +} +function tuple4(): [Array, Array] { + return [new Array(), [1, 2]]; +} +function tuple5(): [i32] { + return [1]; +} +function tuple6(): [[Array]] { + return [[[1, 2]]]; +} +function tuple7(): [x: i32, y: i32] { + return [1, 2]; +} +function tuple8(): [head: i32, tail: [lo: i32, hi: i32]] { + return [1, [2, 3]]; +} +function func1(a: i32, b: i32): [i32, i32] { + return [a, b]; +} +function func2(x: [i32, i32]): [i32, i32] { + return x; +} +function func3(x: [i32, [i32, i32]], y: i32): [i32, [i32, i32]] { + return x; +} +function func4(x: Readonly<[i32, string]>): [void] { + return [void(0)]; +} +function func5(x: Readonly<[Array, Array]>): Readonly<[i32]> { + return [x[1].length]; +} +function func6(x: [i32, i32] | null): [i32, i32] | null { + return x; +} +function func7(x: Readonly<[[Array], [string]]>): Readonly<[[Array], [string]]> { + return x; +} +function func8(x: [left: i32, right: i32]): [first: i32, second: i32] { + return x; +} +type type1 = [i32, i32]; +type type2 = [i32, [i32, i32]]; +type type3 = Readonly<[i32, string]>; +type type4 = [i32, i32] | null; +type type5 = [[i32, i32], [i32, i32]]; +type type6 = [Array, Array, T]; +type type7 = [start: i32, end: [lo: i32, hi: i32]];