Skip to content
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
8ff13cd
chore: update my email
JairusSW Apr 22, 2026
19cacf9
chore(cli): enable `--enable multi-value` flag
JairusSW Apr 22, 2026
4527d65
feat: wire up support for parsing and resolving tuples
JairusSW Apr 22, 2026
c53e659
tests: add tests for tuples
JairusSW Apr 22, 2026
8c480bb
Merge branch 'AssemblyScript:main' into main
JairusSW Apr 22, 2026
6c869a5
tests: organize a bit better and cover more cases
JairusSW Apr 22, 2026
b7dfb8f
feat: add support for named tuple types like `[x: f64, y: f64]`
JairusSW Apr 23, 2026
98fe27f
tests: add tests for named tuple types
JairusSW Apr 23, 2026
39eb3f9
chore(cli): keep multi-value feature as unimplemented in the cli since
JairusSW Apr 23, 2026
e8563bd
fix: tuples should not be parsed if multi-value is disabled
JairusSW Apr 23, 2026
4b2a399
chore: why did i make it readonly lol
JairusSW Apr 23, 2026
5cc7dce
chore: make sure bootstrapping works
JairusSW Apr 23, 2026
5375b24
chore: revert random formatting changes
JairusSW Apr 23, 2026
c8429ce
chore: token check should come before feature
JairusSW Apr 23, 2026
1ca3ad7
chore: update tuple grammer to show named types logically
JairusSW Apr 23, 2026
7c7f000
chore: add options parameter to end of Parser constructor
JairusSW Apr 23, 2026
dda5084
chore: add `Readonly<T>` type helper
JairusSW Apr 23, 2026
5874329
tests: add a few tests for empty tuples and more nullable tests
JairusSW Apr 23, 2026
e5ab232
chore: revert to options workaround
JairusSW Apr 23, 2026
5ef3d89
Update src/parser.ts
JairusSW Apr 23, 2026
d59cbbc
chore: stick with temporary options workaround
JairusSW Apr 23, 2026
301a564
Update src/program.ts
JairusSW Apr 23, 2026
a97c523
Update src/parser.ts
JairusSW Apr 23, 2026
9cd4cb2
chore: fix typo and indenting
JairusSW Apr 23, 2026
f45638a
tests: test each line/col
JairusSW Apr 23, 2026
729cb2d
fix: `readonly` should be ignored for now
JairusSW Apr 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion NOTICE
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ under the licensing terms detailed in LICENSE:
* Adrien Zinger <zinger.ad@gmail.com>
* Ruixiang Chen <xiang19890319@gmail.com>
* Daniel Salvadori <danaugrs@gmail.com>
* Jairus Tanaka <jairus.v.tanaka@outlook.com>
* Jairus Tanaka <me@jairus.dev>
* CountBleck <Mr.YouKnowWhoIAm@protonmail.com>
* Abdul Rauf <abdulraufmujahid@gmail.com>
* Bach Le <bach@bullno1.com>
Expand Down
32 changes: 32 additions & 0 deletions src/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export const enum NodeKind {
// types
NamedType,
FunctionType,
TupleType,
TypeName,
TypeParameter,
Parameter,
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 = <TupleTypeNode>changetype<TypeNode>(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);
}
Expand Down Expand Up @@ -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(
Expand Down
36 changes: 36 additions & 0 deletions src/extra/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
TypeNode,
NamedTypeNode,
FunctionTypeNode,
TupleTypeNode,
TypeName,
TypeParameterNode,

Expand Down Expand Up @@ -134,6 +135,10 @@ export class ASTBuilder {
this.visitFunctionTypeNode(<FunctionTypeNode>node);
break;
}
case NodeKind.TupleType: {
this.visitTupleTypeNode(<TupleTypeNode>node);
break;
}
case NodeKind.TypeParameter: {
this.visitTypeParameter(<TypeParameterNode>node);
break;
Expand Down Expand Up @@ -387,6 +392,10 @@ export class ASTBuilder {
this.visitFunctionTypeNode(<FunctionTypeNode>node);
break;
}
case NodeKind.TupleType: {
this.visitTupleTypeNode(<TupleTypeNode>node);
break;
}
default: assert(false);
}
}
Expand Down Expand Up @@ -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;
Expand Down
62 changes: 60 additions & 2 deletions src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import {
CommonFlags,
Feature,
LIBRARY_PREFIX,
PATH_DELIMITER
} from "./common";
Expand Down Expand Up @@ -42,6 +43,7 @@ import {
TypeName,
NamedTypeNode,
FunctionTypeNode,
TupleTypeNode,
ArrowKind,

Expression,
Expand Down Expand Up @@ -90,6 +92,7 @@ import {

mangleInternalPath
} from "./ast";
import { Options } from "./compiler";

/** Represents a dependee. */
class Dependee {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
);
Comment thread
JairusSW marked this conversation as resolved.
Outdated

// '(' ...
if (token == Token.OpenParen) {
} else if (token == Token.OpenParen) {

// '(' FunctionSignature ')'
let isInnerParenthesized = tn.skip(Token.OpenParen);
Expand Down Expand Up @@ -563,6 +578,42 @@ export class Parser extends DiagnosticEmitter {
return null;
}

// '[' Type (',' Type)* ']'
Comment thread
JairusSW marked this conversation as resolved.
Outdated
} else if (this.options && this.options!.hasFeature(Feature.MultiValue) && token == Token.OpenBracket) {
Comment thread
JairusSW marked this conversation as resolved.
Outdated
let elements: TypeNode[] = [];
let elementNames: (IdentifierExpression | null)[] = [];
let hasElementNames = false;
Comment thread
MaxGraey marked this conversation as resolved.
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(
Expand Down Expand Up @@ -4581,6 +4632,13 @@ function isCircularTypeAlias(name: string, type: TypeNode): bool {
}
break;
}
case NodeKind.TupleType: {
let elements = (<TupleTypeNode>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;
Expand Down
3 changes: 3 additions & 0 deletions src/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Comment thread
JairusSW marked this conversation as resolved.
Outdated
}

/** Module instance. */
Expand Down
31 changes: 31 additions & 0 deletions src/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import {

import {
FunctionTypeNode,
TupleTypeNode,
ParameterKind,
TypeNode,
NodeKind,
Expand Down Expand Up @@ -171,6 +172,10 @@ export class Resolver extends DiagnosticEmitter {
resolved = this.resolveFunctionType(<FunctionTypeNode>node, flow, ctxElement, ctxTypes, reportMode);
break;
}
case NodeKind.TupleType: {
resolved = this.resolveTupleType(<TupleTypeNode>node, flow, ctxElement, ctxTypes, reportMode);
break;
}
default: assert(false);
}
node.currentlyResolving = false;
Expand Down Expand Up @@ -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<string,Type> | 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,
Expand Down
9 changes: 9 additions & 0 deletions tests/compiler/tuple-circular.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"asc_flags": [
"--enable", "multi-value"
],
"stderr": [
"TS2456: Type alias 'Loop' circularly references itself.",
"1 parse error(s)"
]
}
1 change: 1 addition & 0 deletions tests/compiler/tuple-circular.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
type Loop = [Loop, i32];
9 changes: 9 additions & 0 deletions tests/compiler/tuple-disabled.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"asc_flags": [
"--disable", "multi-value"
],
"stderr": [
"TS1110: Type expected.",
"3 parse error(s)"
]
}
3 changes: 3 additions & 0 deletions tests/compiler/tuple-disabled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function tupleDisabled(x: [left: i32, right: i32]): void {}
export type tupleTypeDisabled1 = [i32, i32];
export type tupleTypeDisabled2 = [];
9 changes: 9 additions & 0 deletions tests/compiler/tuple-errors.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"asc_flags": [
"--enable", "multi-value"
],
"stderr": [
"AS100: Not implemented: Tuple types",
"20 compile error(s)"
Comment thread
JairusSW marked this conversation as resolved.
Outdated
]
}
Loading