Skip to content

Commit a5112aa

Browse files
JairusSWMaxGraey
andauthored
feat: properly parse and resolve tuples (#3018)
Co-authored-by: Max Graey <maxgraey@gmail.com>
1 parent ffada5a commit a5112aa

19 files changed

Lines changed: 413 additions & 8 deletions

NOTICE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ under the licensing terms detailed in LICENSE:
5151
* Adrien Zinger <zinger.ad@gmail.com>
5252
* Ruixiang Chen <xiang19890319@gmail.com>
5353
* Daniel Salvadori <danaugrs@gmail.com>
54-
* Jairus Tanaka <jairus.v.tanaka@outlook.com>
54+
* Jairus Tanaka <me@jairus.dev>
5555
* CountBleck <Mr.YouKnowWhoIAm@protonmail.com>
5656
* Abdul Rauf <abdulraufmujahid@gmail.com>
5757
* Bach Le <bach@bullno1.com>

src/ast.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ export const enum NodeKind {
5050
// types
5151
NamedType,
5252
FunctionType,
53+
TupleType,
5354
TypeName,
5455
TypeParameter,
5556
Parameter,
@@ -161,6 +162,15 @@ export abstract class Node {
161162
return new FunctionTypeNode(parameters, returnType, explicitThisType, isNullable, range);
162163
}
163164

165+
static createTupleType(
166+
elements: TypeNode[],
167+
elementNames: (IdentifierExpression | null)[] | null,
168+
isNullable: bool,
169+
range: Range
170+
): TupleTypeNode {
171+
return new TupleTypeNode(elements, elementNames, isNullable, range);
172+
}
173+
164174
static createOmittedType(
165175
range: Range
166176
): NamedTypeNode {
@@ -862,6 +872,12 @@ export abstract class TypeNode extends Node {
862872
if (functionTypeNode.returnType.hasGenericComponent(typeParameterNodes)) return true;
863873
let explicitThisType = functionTypeNode.explicitThisType;
864874
if (explicitThisType && explicitThisType.hasGenericComponent(typeParameterNodes)) return true;
875+
} else if (this.kind == NodeKind.TupleType) {
876+
let tupleTypeNode = <TupleTypeNode>changetype<TypeNode>(this);
877+
let elements = tupleTypeNode.elements;
878+
for (let i = 0, k = elements.length; i < k; ++i) {
879+
if (elements[i].hasGenericComponent(typeParameterNodes)) return true;
880+
}
865881
} else {
866882
assert(false);
867883
}
@@ -928,6 +944,22 @@ export class FunctionTypeNode extends TypeNode {
928944
}
929945
}
930946

947+
/** Represents a tuple type. */
948+
export class TupleTypeNode extends TypeNode {
949+
constructor(
950+
/** Tuple elements. */
951+
public elements: TypeNode[],
952+
/** Tuple element names, if any. */
953+
public elementNames: (IdentifierExpression | null)[] | null,
954+
/** Whether nullable or not. */
955+
isNullable: bool,
956+
/** Source range. */
957+
range: Range
958+
) {
959+
super(NodeKind.TupleType, isNullable, range);
960+
}
961+
}
962+
931963
/** Represents a type parameter. */
932964
export class TypeParameterNode extends Node {
933965
constructor(

src/extra/ast.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
TypeNode,
1616
NamedTypeNode,
1717
FunctionTypeNode,
18+
TupleTypeNode,
1819
TypeName,
1920
TypeParameterNode,
2021

@@ -134,6 +135,10 @@ export class ASTBuilder {
134135
this.visitFunctionTypeNode(<FunctionTypeNode>node);
135136
break;
136137
}
138+
case NodeKind.TupleType: {
139+
this.visitTupleTypeNode(<TupleTypeNode>node);
140+
break;
141+
}
137142
case NodeKind.TypeParameter: {
138143
this.visitTypeParameter(<TypeParameterNode>node);
139144
break;
@@ -387,6 +392,10 @@ export class ASTBuilder {
387392
this.visitFunctionTypeNode(<FunctionTypeNode>node);
388393
break;
389394
}
395+
case NodeKind.TupleType: {
396+
this.visitTupleTypeNode(<TupleTypeNode>node);
397+
break;
398+
}
390399
default: assert(false);
391400
}
392401
}
@@ -450,6 +459,33 @@ export class ASTBuilder {
450459
if (isNullable) sb.push(") | null");
451460
}
452461

462+
visitTupleTypeNode(node: TupleTypeNode): void {
463+
let sb = this.sb;
464+
sb.push("[");
465+
let elements = node.elements;
466+
let elementNames = node.elementNames;
467+
let numElements = elements.length;
468+
if (numElements) {
469+
let name = elementNames ? elementNames[0] : null;
470+
if (name) {
471+
this.visitIdentifierExpression(name);
472+
sb.push(": ");
473+
}
474+
this.visitTypeNode(elements[0]);
475+
for (let i = 1; i < numElements; ++i) {
476+
sb.push(", ");
477+
name = elementNames ? elementNames[i] : null;
478+
if (name) {
479+
this.visitIdentifierExpression(name);
480+
sb.push(": ");
481+
}
482+
this.visitTypeNode(elements[i]);
483+
}
484+
}
485+
sb.push("]");
486+
if (node.isNullable) sb.push(" | null");
487+
}
488+
453489
visitTypeParameter(node: TypeParameterNode): void {
454490
this.visitIdentifierExpression(node.name);
455491
let extendsType = node.extendsType;

src/parser.ts

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import {
1111
CommonFlags,
12+
Feature,
1213
LIBRARY_PREFIX,
1314
PATH_DELIMITER
1415
} from "./common";
@@ -42,6 +43,7 @@ import {
4243
TypeName,
4344
NamedTypeNode,
4445
FunctionTypeNode,
46+
TupleTypeNode,
4547
ArrowKind,
4648

4749
Expression,
@@ -90,6 +92,7 @@ import {
9092

9193
mangleInternalPath
9294
} from "./ast";
95+
import { Options } from "./compiler";
9396

9497
/** Represents a dependee. */
9598
class Dependee {
@@ -118,7 +121,9 @@ export class Parser extends DiagnosticEmitter {
118121
sources: Source[];
119122
/** Current overridden module name. */
120123
currentModuleName: string | null = null;
121-
124+
// TODO: Remove when multi-value feature will enable by default.
125+
/** Compiler options. */
126+
options: Options | null = null;
122127
/** Constructs a new parser. */
123128
constructor(
124129
diagnostics: DiagnosticMessage[] | null = null,
@@ -563,6 +568,49 @@ export class Parser extends DiagnosticEmitter {
563568
return null;
564569
}
565570

571+
// 'readonly' Type
572+
} else if (token == Token.Readonly) {
573+
let innerType = this.parseType(tn, acceptParenthesized, suppressErrors);
574+
if (!innerType) return null;
575+
type = innerType;
576+
type.range.start = startPos;
577+
578+
// '[' ((Identifier ':')? Type (',' (Identifier ':')? Type)*)? ']'
579+
} else if (token == Token.OpenBracket && this.options && this.options!.hasFeature(Feature.MultiValue)) {
580+
let elements: TypeNode[] = [];
581+
let elementNames: (IdentifierExpression | null)[] = [];
582+
let hasElementNames = false;
583+
if (!tn.skip(Token.CloseBracket)) {
584+
do {
585+
let elementName: IdentifierExpression | null = null;
586+
let state = tn.mark();
587+
if (tn.skip(Token.Identifier)) {
588+
let name = tn.readIdentifier();
589+
let nameRange = tn.range();
590+
if (tn.skip(Token.Colon)) {
591+
elementName = Node.createIdentifierExpression(name, nameRange);
592+
hasElementNames = true;
593+
} else {
594+
tn.reset(state);
595+
}
596+
}
597+
let element = this.parseType(tn, true, suppressErrors);
598+
if (!element) return null;
599+
elements.push(element);
600+
elementNames.push(elementName);
601+
} while (tn.skip(Token.Comma));
602+
if (!tn.skip(Token.CloseBracket)) {
603+
if (!suppressErrors) {
604+
this.error(
605+
DiagnosticCode._0_expected,
606+
tn.range(tn.pos), "]"
607+
);
608+
}
609+
return null;
610+
}
611+
}
612+
type = Node.createTupleType(elements, hasElementNames ? elementNames : null, false, tn.range(startPos, tn.pos));
613+
566614
// 'void'
567615
} else if (token == Token.Void) {
568616
type = Node.createNamedType(
@@ -4581,6 +4629,13 @@ function isCircularTypeAlias(name: string, type: TypeNode): bool {
45814629
}
45824630
break;
45834631
}
4632+
case NodeKind.TupleType: {
4633+
let elements = (<TupleTypeNode>type).elements;
4634+
for (let i = 0, k = elements.length; i < k; i++) {
4635+
if (isCircularTypeAlias(name, elements[i])) return true;
4636+
}
4637+
break;
4638+
}
45844639
default: assert(false);
45854640
}
45864641
return false;

src/program.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -390,9 +390,9 @@ export namespace OperatorKind {
390390
case Token.GreaterThan_GreaterThan_Equals: return OperatorKind.BitwiseShr;
391391
case Token.GreaterThan_GreaterThan_GreaterThan:
392392
case Token.GreaterThan_GreaterThan_GreaterThan_Equals: return OperatorKind.BitwiseShrU;
393-
case Token.Equals_Equals:
393+
case Token.Equals_Equals:
394394
case Token.Equals_Equals_Equals: return OperatorKind.Eq;
395-
case Token.Exclamation_Equals:
395+
case Token.Exclamation_Equals:
396396
case Token.Exclamation_Equals_Equals: return OperatorKind.Ne;
397397
case Token.GreaterThan: return OperatorKind.Gt;
398398
case Token.GreaterThan_Equals: return OperatorKind.Ge;
@@ -436,12 +436,15 @@ export class Program extends DiagnosticEmitter {
436436
diagnostics: DiagnosticMessage[] | null = null
437437
) {
438438
super(diagnostics);
439-
this.module = Module.create(options.stackSize > 0, options.sizeTypeRef);
439+
this.module = Module.create(options.stackSize > 0, options.sizeTypeRef);
440440
this.parser = new Parser(this.diagnostics, this.sources);
441441
this.resolver = new Resolver(this);
442442
let nativeFile = new File(this, Source.native);
443443
this.nativeFile = nativeFile;
444444
this.filesByName.set(nativeFile.internalName, nativeFile);
445+
446+
// TODO: temporary workaround. remove after multi-value support is finished
447+
this.parser.options = this.options;
445448
}
446449

447450
/** Module instance. */

src/resolver.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import {
4545

4646
import {
4747
FunctionTypeNode,
48+
TupleTypeNode,
4849
ParameterKind,
4950
TypeNode,
5051
NodeKind,
@@ -171,6 +172,10 @@ export class Resolver extends DiagnosticEmitter {
171172
resolved = this.resolveFunctionType(<FunctionTypeNode>node, flow, ctxElement, ctxTypes, reportMode);
172173
break;
173174
}
175+
case NodeKind.TupleType: {
176+
resolved = this.resolveTupleType(<TupleTypeNode>node, flow, ctxElement, ctxTypes, reportMode);
177+
break;
178+
}
174179
default: assert(false);
175180
}
176181
node.currentlyResolving = false;
@@ -452,6 +457,32 @@ export class Resolver extends DiagnosticEmitter {
452457
return node.isNullable ? signature.type.asNullable() : signature.type;
453458
}
454459

460+
/** Resolves a {@link TupleTypeNode}. */
461+
private resolveTupleType(
462+
/** The type to resolve. */
463+
node: TupleTypeNode,
464+
/** The flow */
465+
flow: Flow | null,
466+
/** Contextual element. */
467+
ctxElement: Element,
468+
/** Contextual types, i.e. `T`. */
469+
ctxTypes: Map<string,Type> | null = null,
470+
/** How to proceed with eventual diagnostics. */
471+
reportMode: ReportMode = ReportMode.Report
472+
): Type | null {
473+
let elements = node.elements;
474+
for (let i = 0, k = elements.length; i < k; ++i) {
475+
if (!this.resolveType(elements[i], flow, ctxElement, ctxTypes, reportMode)) return null;
476+
}
477+
if (reportMode == ReportMode.Report) {
478+
this.error(
479+
DiagnosticCode.Not_implemented_0,
480+
node.range, "Tuple types"
481+
);
482+
}
483+
return null;
484+
}
485+
455486
private resolveBuiltinNotNullableType(
456487
/** The type to resolve. */
457488
node: NamedTypeNode,

tests/compiler/tuple-circular.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"asc_flags": [
3+
"--enable", "multi-value"
4+
],
5+
"stderr": [
6+
"TS2456: Type alias 'Loop' circularly references itself.",
7+
"1 parse error(s)"
8+
]
9+
}

tests/compiler/tuple-circular.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
type Loop = [Loop, i32];

tests/compiler/tuple-disabled.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"asc_flags": [
3+
"--disable", "multi-value"
4+
],
5+
"stderr": [
6+
"TS1110: Type expected.",
7+
"3 parse error(s)"
8+
]
9+
}

tests/compiler/tuple-disabled.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export function tupleDisabled(x: [left: i32, right: i32]): void {}
2+
export type tupleTypeDisabled1 = [i32, i32];
3+
export type tupleTypeDisabled2 = [];

0 commit comments

Comments
 (0)