diff --git a/src/ast.ts b/src/ast.ts index da649de549..04582da82f 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -166,9 +166,10 @@ export abstract class Node { elements: TypeNode[], elementNames: (IdentifierExpression | null)[] | null, isNullable: bool, - range: Range + range: Range, + isReadonly: bool = false ): TupleTypeNode { - return new TupleTypeNode(elements, elementNames, isNullable, range); + return new TupleTypeNode(elements, elementNames, isNullable, range, isReadonly); } static createOmittedType( @@ -954,7 +955,9 @@ export class TupleTypeNode extends TypeNode { /** Whether nullable or not. */ isNullable: bool, /** Source range. */ - range: Range + range: Range, + /** Whether this is a readonly tuple. */ + public isReadonly: bool = false ) { super(NodeKind.TupleType, isNullable, range); } diff --git a/src/compiler.ts b/src/compiler.ts index f99bd5a049..7b7de10ec0 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -446,6 +446,8 @@ export class Compiler extends DiagnosticEmitter { currentParent: Element | null = null; /** Current type in compilation. */ currentType: Type = Type.void; + /** Current multi-value result types in compilation. */ + currentReturnTypes: Type[] | null = null; /** Start function statements. */ currentBody: ExpressionRef[]; /** Counting memory offset. */ @@ -531,6 +533,37 @@ export class Compiler extends DiagnosticEmitter { this.shadowStack = new ShadowStackPass(this); } + private typesEqual(left: Type[] | null, right: Type[] | null): bool { + if (left) { + if (!right) return false; + let numTypes = left.length; + if (numTypes != right.length) return false; + for (let i = 0; i < numTypes; ++i) { + if (!left[i].equals(right[i])) return false; + } + return true; + } + return right == null; + } + + private typesContain(types: Type[], type: Type): bool { + for (let i = 0, k = types.length; i < k; ++i) { + if (types[i] == type) return true; + } + return false; + } + + private returnTypesToString(types: Type[]): string { + let sb = new Array(); + sb.push("readonly ["); + for (let i = 0, k = types.length; i < k; ++i) { + if (i) sb.push(", "); + sb.push(types[i].toString()); + } + sb.push("]"); + return sb.join(""); + } + /** Performs compilation of the underlying {@link Program} to a {@link Module}. */ compile(): Module { let options = this.options; @@ -983,6 +1016,20 @@ export class Compiler extends DiagnosticEmitter { // utilize varargs stub to fill in omitted arguments functionInstance = this.ensureVarargsStub(functionInstance); this.runtimeFeatures |= RuntimeFeatures.setArgumentsLength; + signature = functionInstance.signature; + } + let signatureReturnTypes = signature.returnTypes; + if ( + this.options.bindingsHint && + signatureReturnTypes != null && + signatureReturnTypes.length > 1 + ) { + this.error( + DiagnosticCode.Not_implemented_0, + functionInstance.identifierNode.range, + "Exported multi-value functions are not yet supported with --bindings" + ); + return; } this.compileFunction(functionInstance); if (functionInstance.is(CommonFlags.Compiled)) { @@ -998,7 +1045,7 @@ export class Compiler extends DiagnosticEmitter { let thisType = signature.thisType; if ( thisType && lowerRequiresExportRuntime(thisType) || - liftRequiresExportRuntime(signature.returnType) + !signature.returnTypes && liftRequiresExportRuntime(signature.returnType) ) { this.desiresExportRuntime = true; } else { @@ -1011,7 +1058,17 @@ export class Compiler extends DiagnosticEmitter { } } } - if (functionInstance.signature.returnType.kind == TypeKind.Func) this.module.setClosedWorld(false); + let returnTypes = functionInstance.signature.returnTypes; + if (returnTypes) { + for (let i = 0, k = returnTypes.length; i < k; ++i) { + if (returnTypes[i].kind == TypeKind.Func) { + this.module.setClosedWorld(false); + break; + } + } + } else if (functionInstance.signature.returnType.kind == TypeKind.Func) { + this.module.setClosedWorld(false); + } } return; } @@ -1635,6 +1692,7 @@ export class Compiler extends DiagnosticEmitter { pendingElements.add(instance); let previousType = this.currentType; + let previousReturnTypes = this.currentReturnTypes; let module = this.module; let signature = instance.signature; let bodyNode = instance.prototype.bodyNode; @@ -1691,7 +1749,7 @@ export class Compiler extends DiagnosticEmitter { signature.paramRefs, signature.resultRefs, typesToRefs(instance.getNonParameterLocalTypes()), - module.flatten(stmts, instance.signature.returnType.toRef()) + module.flatten(stmts, instance.signature.resultRefs) ); // imported function @@ -1710,7 +1768,7 @@ export class Compiler extends DiagnosticEmitter { let thisType = signature.thisType; if ( thisType && liftRequiresExportRuntime(thisType) || - lowerRequiresExportRuntime(signature.returnType) + !signature.returnTypes && lowerRequiresExportRuntime(signature.returnType) ) { this.desiresExportRuntime = true; } else { @@ -1760,10 +1818,14 @@ export class Compiler extends DiagnosticEmitter { if (instance.is(CommonFlags.Ambient) || instance.is(CommonFlags.Export)) { // Verify and print warn if signature has v128 type for imported or exported functions let hasVectorValueOperands = signature.hasVectorValueOperands; - if (hasVectorValueOperands) { + let returnTypes = signature.returnTypes; + let hasVectorValueReturn = returnTypes + ? this.typesContain(returnTypes, Type.v128) + : signature.returnType == Type.v128; + if (hasVectorValueOperands || hasVectorValueReturn) { let range: Range; let fnTypeNode = instance.prototype.functionTypeNode; - if (signature.returnType == Type.v128) { + if (hasVectorValueReturn) { range = fnTypeNode.returnType.range; } else { let firstIndex = signature.getVectorValueOperandIndices()[0]; @@ -1778,6 +1840,7 @@ export class Compiler extends DiagnosticEmitter { instance.finalize(module, funcRef); this.currentType = previousType; + this.currentReturnTypes = previousReturnTypes; pendingElements.delete(instance); return true; } @@ -1791,7 +1854,9 @@ export class Compiler extends DiagnosticEmitter { ): bool { let module = this.module; let bodyNode = assert(instance.prototype.bodyNode); - let returnType = instance.signature.returnType; + let signature = instance.signature; + let returnType = signature.returnType; + let returnTypes = signature.returnTypes; let flow = this.currentFlow; let thisLocal = instance.signature.thisType ? assert(flow.lookupLocal(CommonNames.this_)) @@ -1811,16 +1876,23 @@ export class Compiler extends DiagnosticEmitter { // none of the following can be an arrow function assert(!instance.isAny(CommonFlags.Constructor | CommonFlags.Get | CommonFlags.Set)); - let expr = this.compileExpression((bodyNode).expression, returnType, Constraints.ConvImplicit); - if (!flow.canOverflow(expr, returnType)) flow.set(FlowFlags.ReturnsWrapped); - if (flow.isNonnull(expr, returnType)) flow.set(FlowFlags.ReturnsNonNull); + let expression = (bodyNode).expression; + let expr = returnTypes + ? this.compileMultiValueReturnExpression(expression, returnTypes, Constraints.ConvImplicit) + : this.compileExpression(expression, returnType, Constraints.ConvImplicit); + if (!returnTypes) { + if (!flow.canOverflow(expr, returnType)) flow.set(FlowFlags.ReturnsWrapped); + if (flow.isNonnull(expr, returnType)) flow.set(FlowFlags.ReturnsNonNull); + } if (!stmts) stmts = [ expr ]; else stmts.push(expr); if (!flow.is(FlowFlags.Terminates)) { - if (!flow.canOverflow(expr, returnType)) flow.set(FlowFlags.ReturnsWrapped); - if (flow.isNonnull(expr, returnType)) flow.set(FlowFlags.ReturnsNonNull); + if (!returnTypes) { + if (!flow.canOverflow(expr, returnType)) flow.set(FlowFlags.ReturnsWrapped); + if (flow.isNonnull(expr, returnType)) flow.set(FlowFlags.ReturnsNonNull); + } flow.set(FlowFlags.Returns | FlowFlags.Terminates); } } @@ -1885,7 +1957,7 @@ export class Compiler extends DiagnosticEmitter { } // if this is a normal function, make sure that all branches terminate - } else if (returnType != Type.void && !flow.is(FlowFlags.Terminates)) { + } else if ((returnTypes || returnType != Type.void) && !flow.is(FlowFlags.Terminates)) { this.error( DiagnosticCode.A_function_whose_declared_type_is_not_void_must_return_a_value, instance.prototype.functionTypeNode.returnType.range @@ -2842,6 +2914,84 @@ export class Compiler extends DiagnosticEmitter { } } + private compileMultiValueReturnExpression( + expression: Expression, + returnTypes: Type[], + constraints: Constraints + ): ExpressionRef { + if (expression.kind == NodeKind.Literal && (expression).literalKind == LiteralKind.Array) { + return this.compileMultiValueArrayLiteral( + expression, + returnTypes, + constraints + ); + } + + let expr = this.compileExpression(expression, Type.auto); + if (!this.typesEqual(this.currentReturnTypes, returnTypes)) { + let currentReturnTypes = this.currentReturnTypes; + this.error( + DiagnosticCode.Type_0_is_not_assignable_to_type_1, + expression.range, + currentReturnTypes + ? this.returnTypesToString(currentReturnTypes) + : this.currentType.toString(), + this.returnTypesToString(returnTypes) + ); + this.currentType = Type.void; + this.currentReturnTypes = returnTypes; + return this.module.unreachable(); + } + this.currentType = Type.void; + this.currentReturnTypes = returnTypes; + return expr; + } + + private compileMultiValueArrayLiteral( + expression: ArrayLiteralExpression, + returnTypes: Type[], + constraints: Constraints + ): ExpressionRef { + let module = this.module; + let expressions = expression.elementExpressions; + let length = expressions.length; + let numTypes = returnTypes.length; + if (length != numTypes) { + this.error( + DiagnosticCode.Type_0_is_not_assignable_to_type_1, + expression.range, + `array literal of length ${length}`, + this.returnTypesToString(returnTypes) + ); + this.currentType = Type.void; + this.currentReturnTypes = returnTypes; + return module.unreachable(); + } + let values = new Array(length); + let elementConstraints = Constraints.ConvImplicit; + if (constraints & Constraints.MustWrap) elementConstraints |= Constraints.MustWrap; + for (let i = 0; i < length; ++i) { + let elementExpression = expressions[i]; + if (elementExpression.kind == NodeKind.Omitted) { + this.error( + DiagnosticCode.Type_expected, + elementExpression.range + ); + this.currentType = Type.void; + this.currentReturnTypes = returnTypes; + return module.unreachable(); + } + unchecked(values[i] = this.compileExpression( + elementExpression, + returnTypes[i], + elementConstraints + )); + } + this.currentType = Type.void; + this.currentReturnTypes = returnTypes; + return module.tuple_make(values); + } + private compileReturnStatement( statement: ReturnStatement ): ExpressionRef { @@ -2849,22 +2999,29 @@ export class Compiler extends DiagnosticEmitter { let expr: ExpressionRef = 0; let flow = this.currentFlow; let returnType = flow.returnType; + let returnTypes = flow.sourceFunction.signature.returnTypes; let valueExpression = statement.value; if (valueExpression) { let constraints = Constraints.ConvImplicit; if (flow.sourceFunction.is(CommonFlags.ModuleExport)) constraints |= Constraints.MustWrap; - expr = this.compileExpression(valueExpression, returnType, constraints); - if (!flow.canOverflow(expr, returnType)) flow.set(FlowFlags.ReturnsWrapped); - if (flow.isNonnull(expr, returnType)) flow.set(FlowFlags.ReturnsNonNull); + if (returnTypes) { + expr = this.compileMultiValueReturnExpression(valueExpression, returnTypes, constraints); + } else { + expr = this.compileExpression(valueExpression, returnType, constraints); + if (!flow.canOverflow(expr, returnType)) flow.set(FlowFlags.ReturnsWrapped); + if (flow.isNonnull(expr, returnType)) flow.set(FlowFlags.ReturnsNonNull); + } if (flow.sourceFunction.is(CommonFlags.Constructor) && valueExpression.kind != NodeKind.This) { flow.set(FlowFlags.MayReturnNonThis); } - } else if (returnType != Type.void) { + } else if (returnTypes || returnType != Type.void) { this.error( DiagnosticCode.Type_0_is_not_assignable_to_type_1, - statement.range, "void", returnType.toString() + statement.range, + "void", + returnTypes ? this.returnTypesToString(returnTypes) : returnType.toString() ); this.currentType = returnType; return module.unreachable(); @@ -2877,7 +3034,7 @@ export class Compiler extends DiagnosticEmitter { if (flow.isInline) { let inlineReturnLabel = assert(flow.inlineReturnLabel); return expr - ? this.currentType == Type.void + ? this.currentType == Type.void && !this.currentReturnTypes ? module.block(null, [ expr, module.br(inlineReturnLabel) ]) : module.br(inlineReturnLabel, 0, expr) : module.br(inlineReturnLabel); @@ -2885,7 +3042,7 @@ export class Compiler extends DiagnosticEmitter { // Otherwise emit a normal return return expr - ? this.currentType == Type.void + ? this.currentType == Type.void && !this.currentReturnTypes ? module.block(null, [ expr, module.return() ]) : module.return(expr) : module.return(); @@ -3467,6 +3624,7 @@ export class Compiler extends DiagnosticEmitter { expression = (expression).expression; } this.currentType = contextualType; + this.currentReturnTypes = null; if (contextualType == Type.void) constraints |= Constraints.WillDrop; let expr: ExpressionRef; switch (expression.kind) { @@ -3554,6 +3712,23 @@ export class Compiler extends DiagnosticEmitter { } // ensure conversion and wrapping in case the respective function doesn't on its own let currentType = this.currentType; + let currentReturnTypes = this.currentReturnTypes; + if (currentReturnTypes) { + if (contextualType == Type.void && (constraints & Constraints.WillDrop)) { + expr = this.module.drop(expr); + this.currentReturnTypes = null; + } else if (contextualType != Type.auto) { + this.error( + DiagnosticCode.Type_0_is_not_assignable_to_type_1, + expression.range, + this.returnTypesToString(currentReturnTypes), + contextualType.toString() + ); + expr = this.module.unreachable(); + this.currentReturnTypes = null; + } + currentType = this.currentType = Type.void; + } let wrap = (constraints & Constraints.MustWrap) != 0; if (currentType != contextualType.nonNullableType) { // allow assigning non-nullable to nullable if (constraints & Constraints.ConvExplicit) { @@ -6547,11 +6722,13 @@ export class Compiler extends DiagnosticEmitter { // Free any new scoped locals and reset to the original flow let returnType = flow.returnType; + let returnTypes = flow.sourceFunction.signature.returnTypes; this.currentFlow = previousFlow; // Create an outer block that we can break to when returning a value out of order this.currentType = returnType; - return module.block(flow.inlineReturnLabel, body, returnType.toRef()); + this.currentReturnTypes = returnTypes; + return module.block(flow.inlineReturnLabel, body, instance.signature.resultRefs); } /** Makes sure that the arguments length helper global is present. */ @@ -6577,7 +6754,6 @@ export class Compiler extends DiagnosticEmitter { let originalSignature = original.signature; let originalParameterTypes = originalSignature.parameterTypes; let originalParameterDeclarations = original.prototype.functionTypeNode.parameters; - let returnType = originalSignature.returnType; let isInstance = original.is(CommonFlags.Instance); // arguments excl. `this`, operands incl. `this` @@ -6690,7 +6866,7 @@ export class Compiler extends DiagnosticEmitter { stub.signature.paramRefs, stub.signature.resultRefs, typesToRefs(stub.getNonParameterLocalTypes()), - module.flatten(stmts, returnType.toRef()) + module.flatten(stmts, stub.signature.resultRefs) ); stub.set(CommonFlags.Compiled); stub.finalize(module, funcRef); @@ -6783,13 +6959,14 @@ export class Compiler extends DiagnosticEmitter { let calledName = needsVarargsStub ? this.ensureVarargsStub(overrideInstance).internalName : overrideInstance.internalName; - let returnTypeRef = overrideSignature.returnType.toRef(); + let returnTypeRef = overrideSignature.resultRefs; + let returnTypes = overrideSignature.returnTypes; let stmts = new Array(); if (needsVarargsStub) { // Safe to prepend since paramExprs are local.get's stmts.push(module.global_set(this.ensureArgumentsLength(), module.i32(numParameters))); } - if (returnType == Type.void) { + if (!returnTypes && returnType == Type.void) { stmts.push( module.call(calledName, paramExprs, returnTypeRef) ); @@ -6838,7 +7015,7 @@ export class Compiler extends DiagnosticEmitter { for (let i = 0, k = parameterTypes.length; i < k; ++i) { paramExprs[1 + i] = module.local_get(1 + i, parameterTypes[i].toRef()); } - body = module.call(instance.internalName, paramExprs, returnType.toRef()); + body = module.call(instance.internalName, paramExprs, stub.signature.resultRefs); // Otherwise trap } else { @@ -6856,7 +7033,7 @@ export class Compiler extends DiagnosticEmitter { module.block(null, [ builder.render(tempIndex), body - ], returnType.toRef()) + ], stub.signature.resultRefs) ); stub.set(CommonFlags.Compiled); } @@ -7000,7 +7177,8 @@ export class Compiler extends DiagnosticEmitter { instance = this.ensureVarargsStub(instance); if (!this.compileFunction(instance)) return module.unreachable(); instance.flow.flags = original.flow.flags; - let returnTypeRef = returnType.toRef(); + let returnTypes = instance.signature.returnTypes; + let resultRefs = instance.signature.resultRefs; // We know the last operand is optional and omitted, so inject setting // ~argumentsLength into that operand, which is always safe. let lastOperand = operands[maxOperands - 1]; @@ -7011,12 +7189,14 @@ export class Compiler extends DiagnosticEmitter { lastOperand ], lastOperandType.toRef()); this.operandsTostack(instance.signature, operands); - let expr = module.call(instance.internalName, operands, returnTypeRef); - if (returnType != Type.void && immediatelyDropped) { + let expr = module.call(instance.internalName, operands, resultRefs); + if ((returnTypes || returnType != Type.void) && immediatelyDropped) { expr = module.drop(expr); this.currentType = Type.void; + this.currentReturnTypes = null; } else { this.currentType = returnType; + this.currentReturnTypes = returnTypes; } return expr; } @@ -7028,8 +7208,9 @@ export class Compiler extends DiagnosticEmitter { } if (operands) this.operandsTostack(instance.signature, operands); - let expr = module.call(instance.internalName, operands, returnType.toRef()); + let expr = module.call(instance.internalName, operands, instance.signature.resultRefs); this.currentType = returnType; + this.currentReturnTypes = instance.signature.returnTypes; return expr; } @@ -7147,8 +7328,9 @@ export class Compiler extends DiagnosticEmitter { signature.resultRefs ); this.currentType = returnType; + this.currentReturnTypes = signature.returnTypes; stmts.push(expr); - return module.flatten(stmts, returnType.toRef()); + return module.flatten(stmts, signature.resultRefs); } private compileCommaExpression( @@ -10093,8 +10275,20 @@ export class Compiler extends DiagnosticEmitter { supported = false; } } - if (!this.program.checkTypeSupported(signature.returnType, reportNode.returnType)) { - supported = false; + let returnTypes = signature.returnTypes; + if (returnTypes) { + if (!this.program.checkFeatureEnabled(Feature.MultiValue, reportNode.returnType)) { + supported = false; + } + for (let i = 0, k = returnTypes.length; i < k; ++i) { + if (!this.program.checkTypeSupported(returnTypes[i], reportNode.returnType)) { + supported = false; + } + } + } else { + if (!this.program.checkTypeSupported(signature.returnType, reportNode.returnType)) { + supported = false; + } } return supported; } diff --git a/src/extra/ast.ts b/src/extra/ast.ts index 699b2e0c71..07a07f02cc 100644 --- a/src/extra/ast.ts +++ b/src/extra/ast.ts @@ -461,6 +461,7 @@ export class ASTBuilder { visitTupleTypeNode(node: TupleTypeNode): void { let sb = this.sb; + if (node.isReadonly) sb.push("readonly "); sb.push("["); let elements = node.elements; let elementNames = node.elementNames; diff --git a/src/module.ts b/src/module.ts index def4e780bf..7fa7498ce5 100644 --- a/src/module.ts +++ b/src/module.ts @@ -4083,25 +4083,40 @@ function prepareType(builder: binaryen.TypeBuilderRef, seen: Map 1) { let cArrPT = allocPtrArray(paramTypes); diff --git a/src/parser.ts b/src/parser.ts index 157351cd3a..71ed4c5f71 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -572,6 +572,7 @@ export class Parser extends DiagnosticEmitter { } else if (token == Token.Readonly) { let innerType = this.parseType(tn, acceptParenthesized, suppressErrors); if (!innerType) return null; + if (innerType.kind == NodeKind.TupleType) (innerType).isReadonly = true; type = innerType; type.range.start = startPos; @@ -609,7 +610,7 @@ export class Parser extends DiagnosticEmitter { return null; } } - type = Node.createTupleType(elements, hasElementNames ? elementNames : null, false, tn.range(startPos, tn.pos)); + type = Node.createTupleType(elements, hasElementNames ? elementNames : null, false, tn.range(startPos, tn.pos), false); // 'void' } else if (token == Token.Void) { diff --git a/src/program.ts b/src/program.ts index f5d9b7cae6..ed485727ce 100644 --- a/src/program.ts +++ b/src/program.ts @@ -2022,9 +2022,19 @@ export class Program extends DiagnosticEmitter { return false; } } - let returnType = signatureReference.returnType; - if (!this.checkTypeSupported(returnType, reportNode)) { - return false; + let returnTypes = signatureReference.returnTypes; + if (returnTypes) { + if (!this.checkFeatureEnabled(Feature.MultiValue, reportNode)) return false; + for (let i = 0, k = returnTypes.length; i < k; ++i) { + if (!this.checkTypeSupported(returnTypes[i], reportNode)) { + return false; + } + } + } else { + let returnType = signatureReference.returnType; + if (!this.checkTypeSupported(returnType, reportNode)) { + return false; + } } } } diff --git a/src/resolver.ts b/src/resolver.ts index 9576a4768f..ce75bc6479 100644 --- a/src/resolver.ts +++ b/src/resolver.ts @@ -435,6 +435,7 @@ export class Resolver extends DiagnosticEmitter { } let returnTypeNode = node.returnType; let returnType: Type | null; + let returnTypes: Type[] | null = null; if (isTypeOmitted(returnTypeNode)) { if (reportMode == ReportMode.Report) { this.error( @@ -444,17 +445,84 @@ export class Resolver extends DiagnosticEmitter { } returnType = Type.void; } else { - returnType = this.resolveType( - returnTypeNode, + if (returnTypeNode.kind == NodeKind.TupleType && (returnTypeNode).isReadonly) { + returnType = Type.void; + returnTypes = this.resolveMultiValueReturnTypes(returnTypeNode, flow, ctxElement, ctxTypes, reportMode); + if (!returnTypes) return null; + } else { + returnType = this.resolveType(returnTypeNode, flow, ctxElement, ctxTypes, reportMode); + } + if (!returnType) return null; + } + let signature = Signature.create(this.program, parameterTypes, returnType, thisType, requiredParameters, hasRest, returnTypes); + return node.isNullable ? signature.type.asNullable() : signature.type; + } + + /** Resolves a readonly tuple return annotation to multiple return types. */ + private resolveMultiValueReturnTypes( + /** The readonly tuple return 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 { + if (node.isNullable) { + if (reportMode == ReportMode.Report) { + this.error( + DiagnosticCode.Not_implemented_0, + node.range, "Nullable multi-value returns" + ); + } + return null; + } + let elements = node.elements; + let numElements = elements.length; + if (numElements < 2) { + if (reportMode == ReportMode.Report) { + this.error( + DiagnosticCode.Not_implemented_0, + node.range, "Multi-value returns with fewer than two values" + ); + } + return null; + } + let types = new Array(numElements); + for (let i = 0; i < numElements; ++i) { + let elementNode = elements[i]; + if (elementNode.kind == NodeKind.TupleType) { + if (reportMode == ReportMode.Report) { + this.error( + DiagnosticCode.Not_implemented_0, + elementNode.range, "Nested tuple types" + ); + } + return null; + } + let elementType = this.resolveType( + elementNode, flow, ctxElement, ctxTypes, reportMode ); - if (!returnType) return null; + if (!elementType) return null; + if (elementType == Type.void) { + if (reportMode == ReportMode.Report) { + this.error( + DiagnosticCode.Type_0_is_illegal_in_this_context, + elementNode.range, elementType.toString() + ); + } + return null; + } + types[i] = elementType; } - let signature = Signature.create(this.program, parameterTypes, returnType, thisType, requiredParameters, hasRest); - return node.isNullable ? signature.type.asNullable() : signature.type; + return types; } /** Resolves a {@link TupleTypeNode}. */ @@ -2892,6 +2960,7 @@ export class Resolver extends DiagnosticEmitter { // resolve return type let returnType: Type; + let returnTypes: Type[] | null = null; if (prototype.is(CommonFlags.Set)) { returnType = Type.void; // not annotated } else if (prototype.is(CommonFlags.Constructor)) { @@ -2907,18 +2976,18 @@ export class Resolver extends DiagnosticEmitter { } return null; } - let type = this.resolveType( - typeNode, - null, - prototype.parent, // relative to function - ctxTypes, - reportMode - ); - if (!type) return null; - returnType = type; + if (typeNode.kind == NodeKind.TupleType && (typeNode).isReadonly) { + returnType = Type.void; + returnTypes = this.resolveMultiValueReturnTypes(typeNode, null, prototype.parent, ctxTypes, reportMode); + if (!returnTypes) return null; + } else { + let type = this.resolveType(typeNode, null, prototype.parent, ctxTypes, reportMode); + if (!type) return null; + returnType = type; + } } - let signature = Signature.create(this.program, parameterTypes, returnType, thisType, requiredParameters, hasRest); + let signature = Signature.create(this.program, parameterTypes, returnType, thisType, requiredParameters, hasRest, returnTypes); let nameInclTypeParameters = prototype.name; if (instanceKey.length) nameInclTypeParameters += `<${instanceKey}>`; diff --git a/src/types.ts b/src/types.ts index 6a07f6fbc8..3531f1be15 100644 --- a/src/types.ts +++ b/src/types.ts @@ -946,6 +946,8 @@ export class Signature { requiredParameters: i32 = parameterTypes ? parameterTypes.length : 0, /** Whether the last parameter is a rest parameter. */ hasRest: bool = false, + /** Return types, if this signature returns multiple values. */ + returnTypes: Type[] | null = null, ): Signature { // get the usize type, and the type of the signature let usizeType = program.options.usizeType; @@ -960,7 +962,7 @@ export class Signature { let nextId = program.nextSignatureId; // construct the signature and calculate it's unique key - let signature = new Signature(program, parameterTypes, returnType, thisType, requiredParameters, hasRest, nextId, type); + let signature = new Signature(program, parameterTypes, returnType, thisType, requiredParameters, hasRest, nextId, type, returnTypes ); let uniqueKey = signature.toString(); // check if it exists, and return it @@ -995,6 +997,8 @@ export class Signature { public readonly id: u32, /** Respective function type. */ public readonly type: Type, + /** Return types, if this signature returns multiple values. */ + public readonly returnTypes: Type[] | null = null, ) {} get paramRefs(): TypeRef { @@ -1016,6 +1020,8 @@ export class Signature { } get resultRefs(): TypeRef { + let returnTypes = this.returnTypes; + if (returnTypes) return createType(typesToRefs(returnTypes)); return this.returnType.toRef(); } @@ -1035,7 +1041,7 @@ export class Signature { if (this.hasRest != other.hasRest) return false; // check return type - if (!this.returnType.equals(other.returnType)) return false; + if (!signatureReturnTypesEqual(this, other)) return false; // check parameter types let selfParameterTypes = this.parameterTypes; @@ -1069,11 +1075,7 @@ export class Signature { if (this.hasRest != target.hasRest) return false; // TODO // check return type (covariant) - let thisReturnType = this.returnType; - let targetReturnType = target.returnType; - if (!(thisReturnType == targetReturnType || thisReturnType.isAssignableTo(targetReturnType))) { - return false; - } + if (!signatureReturnTypesAssignableTo(this, target)) return false; // check parameter types (invariant) let thisParameterTypes = this.parameterTypes; let targetParameterTypes = target.parameterTypes; @@ -1176,7 +1178,17 @@ export class Signature { } } sb.push(validWat ? "%29=>" : ") => "); - sb.push(this.returnType.toString(validWat)); + let returnTypes = this.returnTypes; + if (returnTypes) { + sb.push("readonly ["); + for (let i = 0, k = returnTypes.length; i < k; ++i) { + if (i) sb.push(validWat ? "," : ", "); + sb.push(returnTypes[i].toString(validWat)); + } + sb.push("]"); + } else { + sb.push(this.returnType.toString(validWat)); + } return sb.join(""); } @@ -1194,7 +1206,47 @@ export class Signature { this.returnType, this.thisType, requiredParameters, - hasRest + hasRest, + this.returnTypes ); } } + +function signatureReturnTypesEqual(left: Signature, right: Signature): bool { + let leftReturnTypes = left.returnTypes; + let rightReturnTypes = right.returnTypes; + if (leftReturnTypes) { + return rightReturnTypes != null && typesEqual(leftReturnTypes, rightReturnTypes); + } + return !rightReturnTypes && left.returnType.equals(right.returnType); +} + +function signatureReturnTypesAssignableTo(left: Signature, right: Signature): bool { + let leftReturnTypes = left.returnTypes; + let rightReturnTypes = right.returnTypes; + if (leftReturnTypes) { + return rightReturnTypes != null && typesAssignableTo(leftReturnTypes, rightReturnTypes); + } + if (rightReturnTypes) return false; + let leftReturnType = left.returnType; + let rightReturnType = right.returnType; + return leftReturnType == rightReturnType || leftReturnType.isAssignableTo(rightReturnType); +} + +function typesEqual(left: Type[], right: Type[]): bool { + let numTypes = left.length; + if (numTypes != right.length) return false; + for (let i = 0; i < numTypes; ++i) { + if (!left[i].equals(right[i])) return false; + } + return true; +} + +function typesAssignableTo(left: Type[], right: Type[]): bool { + let numTypes = left.length; + if (numTypes != right.length) return false; + for (let i = 0; i < numTypes; ++i) { + if (!left[i].isAssignableTo(right[i])) return false; + } + return true; +} diff --git a/test.sh b/test.sh new file mode 100755 index 0000000000..8e5c018411 --- /dev/null +++ b/test.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ASC="$ROOT_DIR/bin/asc.js" +TS_FILE="$ROOT_DIR/test.ts" +WASM_FILE="$ROOT_DIR/test.wasm" +WAT_FILE="$ROOT_DIR/test.wat" + +echo "Compiling $TS_FILE -> $WASM_FILE and $WAT_FILE" +node --enable-source-maps --no-warnings "$ASC" "$TS_FILE" \ + --enable multi-value \ + --outFile "$WASM_FILE" \ + --textFile "$WAT_FILE" + +echo +echo "Running exports with wasmtime" +run() { + local fn="$1" + shift + echo "\$ wasmtime run --invoke $fn $WASM_FILE $*" + wasmtime run --invoke "$fn" "$WASM_FILE" "$@" + echo +} + +run pairI32 3 7 +run foo 10 20 +run pairI64 5 9 +run mixI32I64 11 22 +run forward 1 2 diff --git a/test.ts b/test.ts new file mode 100644 index 0000000000..0a99fbe418 --- /dev/null +++ b/test.ts @@ -0,0 +1,23 @@ +export function pairI32(a: i32, b: i32): readonly [i32, i32] { + return [a, b]; +} + +export function swap(a: i32, b: i32): readonly [i32, i32] { + return [b, a]; +} + +export function foo(a: i32, b: i32): readonly [i32, i32] { + return swap(a, b); +} + +export function pairI64(a: i64, b: i64): readonly [i64, i64] { + return [a, b]; +} + +export function mixI32I64(a: i32, b: i64): readonly [i32, i64] { + return [a + 1, b + 2]; +} + +export function forward(a: i32, b: i32): readonly [i32, i32] { + return pairI32(a + 10, b + 20); +} diff --git a/test.wasm b/test.wasm new file mode 100644 index 0000000000..8c37ae1ddc Binary files /dev/null and b/test.wasm differ diff --git a/test.wat b/test.wat new file mode 100644 index 0000000000..e87cd35d00 --- /dev/null +++ b/test.wat @@ -0,0 +1,62 @@ +(module + (type $0 (func (param i32 i32) (result i32 i32))) + (type $1 (func (param i64 i64) (result i64 i64))) + (type $2 (func (param i32 i64) (result i32 i64))) + (global $~lib/memory/__data_end i32 (i32.const 8)) + (global $~lib/memory/__stack_pointer (mut i32) (i32.const 32776)) + (global $~lib/memory/__heap_base i32 (i32.const 32776)) + (memory $0 0) + (table $0 1 1 funcref) + (elem $0 (i32.const 1)) + (export "pairI32" (func $test/pairI32)) + (export "swap" (func $test/swap)) + (export "foo" (func $test/foo)) + (export "pairI64" (func $test/pairI64)) + (export "mixI32I64" (func $test/mixI32I64)) + (export "forward" (func $test/forward)) + (export "memory" (memory $0)) + (func $test/pairI32 (param $0 i32) (param $1 i32) (result i32 i32) + local.get $0 + local.get $1 + tuple.make 2 + return + ) + (func $test/swap (param $0 i32) (param $1 i32) (result i32 i32) + local.get $1 + local.get $0 + tuple.make 2 + return + ) + (func $test/foo (param $0 i32) (param $1 i32) (result i32 i32) + local.get $0 + local.get $1 + call $test/swap + return + ) + (func $test/pairI64 (param $0 i64) (param $1 i64) (result i64 i64) + local.get $0 + local.get $1 + tuple.make 2 + return + ) + (func $test/mixI32I64 (param $0 i32) (param $1 i64) (result i32 i64) + local.get $0 + i32.const 1 + i32.add + local.get $1 + i64.const 2 + i64.add + tuple.make 2 + return + ) + (func $test/forward (param $0 i32) (param $1 i32) (result i32 i32) + local.get $0 + i32.const 10 + i32.add + local.get $1 + i32.const 20 + i32.add + call $test/pairI32 + return + ) +) diff --git a/tests/compiler/tuple-bindings.json b/tests/compiler/tuple-bindings.json new file mode 100644 index 0000000000..396f4679fc --- /dev/null +++ b/tests/compiler/tuple-bindings.json @@ -0,0 +1,10 @@ +{ + "asc_flags": [ + "--enable", "multi-value", + "--bindings", "raw" + ], + "stderr": [ + "AS100: Not implemented: Exported multi-value functions are not yet supported with --bindings", + "in tuple-bindings.ts(1,17)" + ] +} diff --git a/tests/compiler/tuple-bindings.ts b/tests/compiler/tuple-bindings.ts new file mode 100644 index 0000000000..55e08c7d62 --- /dev/null +++ b/tests/compiler/tuple-bindings.ts @@ -0,0 +1,3 @@ +export function tupleBindingsUnsupported(a: i32, b: i64): readonly [i32, i64] { + return [a, b]; +} diff --git a/tests/compiler/tuple-errors.json b/tests/compiler/tuple-errors.json index bf732842af..9c0ac6840e 100644 --- a/tests/compiler/tuple-errors.json +++ b/tests/compiler/tuple-errors.json @@ -32,6 +32,17 @@ "in tuple-errors.ts(53,35)", "in tuple-errors.ts(56,35)", "in tuple-errors.ts(60,39)", - "in tuple-errors.ts(63,39)" + "in tuple-errors.ts(63,39)", + "in tuple-errors.ts(67,13)", + "AS100: Not implemented: Tuple types", + "in tuple-errors.ts(69,52)", + "AS100: Not implemented: Multi-value returns with fewer than two values", + "in tuple-errors.ts(71,46)", + "AS100: Not implemented: Multi-value returns with fewer than two values", + "in tuple-errors.ts(74,46)", + "AS100: Not implemented: Nested tuple types", + "in tuple-errors.ts(77,58)", + "AS100: Not implemented: Nullable multi-value returns", + "in tuple-errors.ts(80,45)" ] } diff --git a/tests/compiler/tuple-errors.ts b/tests/compiler/tuple-errors.ts index 2bab1e6bb2..4e5d5eaedc 100644 --- a/tests/compiler/tuple-errors.ts +++ b/tests/compiler/tuple-errors.ts @@ -63,3 +63,20 @@ export function TupleTypeMismatch1(x: [i32, f32]): [f32, i32] { export function TupleTypeMismatch2(x: [f64, f32]): [f32, f64] { return x; } + +export type ReadonlyTupleAliasUnimplemented = readonly [i32, i32]; + +export function ReadonlyTupleParamUnimplemented(x: readonly [i32, i32]): void {} + +export function MultiValueReturnTooShort1(): readonly [] { + return []; +} +export function MultiValueReturnTooShort2(): readonly [i32] { + return [0]; +} +export function MultiValueReturnNested(): readonly [i32, readonly [i32, i32]] { + return [0, [1, 2]]; +} +export function MultiValueReturnNullable(): readonly [i32, i32] | null { + return [0, 1]; +} diff --git a/tests/compiler/tuple-type.debug.wat b/tests/compiler/tuple-type.debug.wat new file mode 100644 index 0000000000..875ad2e382 --- /dev/null +++ b/tests/compiler/tuple-type.debug.wat @@ -0,0 +1,164 @@ +(module + (type $0 (func (param i32 i64) (result i32 i64))) + (type $1 (func (result i32))) + (type $2 (func (result i32 i64))) + (type $3 (func (param i32 i32 i64) (result i32 i64))) + (type $4 (func (param f32 f64) (result f32 f64))) + (type $5 (func (param i32 i64))) + (type $6 (func (param i32 i32 i32 i32))) + (type $7 (func)) + (import "env" "abort" (func $~lib/builtins/abort (param i32 i32 i32 i32))) + (global $tuple-type/order (mut i32) (i32.const 0)) + (global $~argumentsLength (mut i32) (i32.const 0)) + (global $~lib/memory/__data_end i32 (i32.const 44)) + (global $~lib/memory/__stack_pointer (mut i32) (i32.const 32812)) + (global $~lib/memory/__heap_base i32 (i32.const 32812)) + (memory $0 1) + (data $0 (i32.const 12) "\1c\00\00\00\00\00\00\00\00\00\00\00\04\00\00\00\08\00\00\00\01\00\00\00\00\00\00\00\00\00\00\00") + (table $0 2 2 funcref) + (elem $0 (i32.const 1) $tuple-type/pair) + (export "pair" (func $tuple-type/pair)) + (export "floats" (func $tuple-type/floats)) + (export "forward" (func $tuple-type/forward)) + (export "named" (func $tuple-type/named)) + (export "dropCall" (func $tuple-type/dropCall)) + (export "indirectOrder" (func $tuple-type/indirectOrder)) + (export "evaluationOrder" (func $tuple-type/evaluationOrder)) + (export "stackPointer" (func $tuple-type/stackPointer)) + (export "memory" (memory $0)) + (export "indirect" (func $export:tuple-type/indirect)) + (func $tuple-type/pair (param $a i32) (param $b i64) (result i32 i64) + local.get $a + local.get $b + tuple.make 2 + return + ) + (func $tuple-type/floats (param $a f32) (param $b f64) (result f32 f64) + local.get $a + local.get $b + tuple.make 2 + return + ) + (func $tuple-type/forward (param $a i32) (param $b i64) (result i32 i64) + local.get $a + local.get $b + call $tuple-type/pair + return + ) + (func $tuple-type/named (param $a i32) (param $b i64) (result i32 i64) + local.get $a + local.get $b + tuple.make 2 + return + ) + (func $tuple-type/indirect (param $fn i32) (param $a i32) (param $b i64) (result i32 i64) + local.get $a + local.get $b + i32.const 2 + global.set $~argumentsLength + local.get $fn + i32.load + call_indirect (type $0) + return + ) + (func $tuple-type/dropCall (param $a i32) (param $b i64) + local.get $a + local.get $b + call $tuple-type/pair + tuple.drop 2 + ) + (func $tuple-type/choosePair (result i32) + global.get $tuple-type/order + i32.const 10 + i32.mul + i32.const 1 + i32.add + global.set $tuple-type/order + i32.const 32 + return + ) + (func $tuple-type/argument (result i32) + global.get $tuple-type/order + i32.const 10 + i32.mul + i32.const 2 + i32.add + global.set $tuple-type/order + i32.const 7 + return + ) + (func $tuple-type/evaluationOrder (result i32) + global.get $tuple-type/order + return + ) + (func $tuple-type/stackPointer (result i32) + global.get $~lib/memory/__stack_pointer + return + ) + (func $~stack_check + global.get $~lib/memory/__stack_pointer + global.get $~lib/memory/__data_end + i32.lt_s + if + i32.const 32832 + i32.const 32880 + i32.const 1 + i32.const 1 + call $~lib/builtins/abort + unreachable + end + ) + (func $tuple-type/indirectOrder (result i32 i64) + (local $0 i32) + (local $1 (tuple i32 i64)) + global.get $~lib/memory/__stack_pointer + i32.const 4 + i32.sub + global.set $~lib/memory/__stack_pointer + call $~stack_check + global.get $~lib/memory/__stack_pointer + i32.const 0 + i32.store + i32.const 0 + global.set $tuple-type/order + global.get $~lib/memory/__stack_pointer + call $tuple-type/choosePair + local.tee $0 + i32.store + call $tuple-type/argument + i64.const 9 + i32.const 2 + global.set $~argumentsLength + local.get $0 + i32.load + call_indirect (type $0) + local.set $1 + global.get $~lib/memory/__stack_pointer + i32.const 4 + i32.add + global.set $~lib/memory/__stack_pointer + local.get $1 + return + ) + (func $export:tuple-type/indirect (param $0 i32) (param $1 i32) (param $2 i64) (result i32 i64) + (local $3 (tuple i32 i64)) + global.get $~lib/memory/__stack_pointer + i32.const 4 + i32.sub + global.set $~lib/memory/__stack_pointer + call $~stack_check + global.get $~lib/memory/__stack_pointer + local.get $0 + i32.store + local.get $0 + local.get $1 + local.get $2 + call $tuple-type/indirect + local.set $3 + global.get $~lib/memory/__stack_pointer + i32.const 4 + i32.add + global.set $~lib/memory/__stack_pointer + local.get $3 + ) +) diff --git a/tests/compiler/tuple-type.js b/tests/compiler/tuple-type.js new file mode 100644 index 0000000000..5d22565958 --- /dev/null +++ b/tests/compiler/tuple-type.js @@ -0,0 +1,14 @@ +import assert from "node:assert/strict"; + +export function postInstantiate({ exports: e }) { + assert.deepEqual(e.pair(3, 7n), [3, 7n]); + assert.deepEqual(e.floats(1.5, 2.5), [1.5, 2.5]); + assert.deepEqual(e.forward(3, 7n), [3, 7n]); + assert.deepEqual(e.named(3, 7n), [3, 7n]); + const stackPointer = e.stackPointer(); + for (let i = 0; i < 100; ++i) { + assert.deepEqual(e.indirectOrder(), [7, 9n]); + assert.equal(e.evaluationOrder(), 12); + } + assert.equal(e.stackPointer(), stackPointer); +} diff --git a/tests/compiler/tuple-type.json b/tests/compiler/tuple-type.json index 8a83de2d20..37e0cad9f0 100644 --- a/tests/compiler/tuple-type.json +++ b/tests/compiler/tuple-type.json @@ -1,7 +1,5 @@ { "asc_flags": [ "--enable", "multi-value" - ], - "stderr": [ ] } diff --git a/tests/compiler/tuple-type.release.wat b/tests/compiler/tuple-type.release.wat new file mode 100644 index 0000000000..e6e36ca867 --- /dev/null +++ b/tests/compiler/tuple-type.release.wat @@ -0,0 +1,118 @@ +(module + (type $0 (func (param i32 i64) (result i32 i64))) + (type $1 (func (result i32 i64))) + (type $2 (func (result i32))) + (type $3 (func (param i32 i32 i32 i32))) + (type $4 (func (param f32 f64) (result f32 f64))) + (type $5 (func (param i32 i64))) + (type $6 (func (param i32 i32 i64) (result i32 i64))) + (import "env" "abort" (func $~lib/builtins/abort (param i32 i32 i32 i32))) + (global $tuple-type/order (mut i32) (i32.const 0)) + (global $~lib/memory/__stack_pointer (mut i32) (i32.const 33836)) + (memory $0 1) + (data $0 (i32.const 1036) "\1c") + (data $0.1 (i32.const 1048) "\04\00\00\00\08\00\00\00\01") + (table $0 2 2 funcref) + (elem $0 (i32.const 1) $tuple-type/pair) + (export "pair" (func $tuple-type/pair)) + (export "floats" (func $tuple-type/floats)) + (export "forward" (func $tuple-type/pair)) + (export "named" (func $tuple-type/pair)) + (export "dropCall" (func $tuple-type/dropCall)) + (export "indirectOrder" (func $tuple-type/indirectOrder)) + (export "evaluationOrder" (func $tuple-type/evaluationOrder)) + (export "stackPointer" (func $tuple-type/stackPointer)) + (export "memory" (memory $0)) + (export "indirect" (func $export:tuple-type/indirect)) + (func $tuple-type/pair (param $0 i32) (param $1 i64) (result i32 i64) + local.get $0 + local.get $1 + tuple.make 2 + ) + (func $tuple-type/stackPointer (result i32) + global.get $~lib/memory/__stack_pointer + ) + (func $tuple-type/indirectOrder (result i32 i64) + (local $0 (tuple i32 i64)) + global.get $~lib/memory/__stack_pointer + i32.const 4 + i32.sub + global.set $~lib/memory/__stack_pointer + global.get $~lib/memory/__stack_pointer + i32.const 1068 + i32.lt_s + if + i32.const 33856 + i32.const 33904 + i32.const 1 + i32.const 1 + call $~lib/builtins/abort + unreachable + end + global.get $~lib/memory/__stack_pointer + i32.const 0 + i32.store + i32.const 0 + global.set $tuple-type/order + i32.const 1 + global.set $tuple-type/order + global.get $~lib/memory/__stack_pointer + i32.const 1056 + i32.store + i32.const 12 + global.set $tuple-type/order + i32.const 7 + i64.const 9 + i32.const 1056 + i32.load + call_indirect (type $0) + local.set $0 + global.get $~lib/memory/__stack_pointer + i32.const 4 + i32.add + global.set $~lib/memory/__stack_pointer + local.get $0 + ) + (func $tuple-type/floats (param $0 f32) (param $1 f64) (result f32 f64) + local.get $0 + local.get $1 + tuple.make 2 + ) + (func $tuple-type/evaluationOrder (result i32) + global.get $tuple-type/order + ) + (func $tuple-type/dropCall (param $0 i32) (param $1 i64) + ) + (func $export:tuple-type/indirect (param $0 i32) (param $1 i32) (param $2 i64) (result i32 i64) + (local $3 (tuple i32 i64)) + global.get $~lib/memory/__stack_pointer + i32.const 4 + i32.sub + global.set $~lib/memory/__stack_pointer + global.get $~lib/memory/__stack_pointer + i32.const 1068 + i32.lt_s + if + i32.const 33856 + i32.const 33904 + i32.const 1 + i32.const 1 + call $~lib/builtins/abort + unreachable + end + global.get $~lib/memory/__stack_pointer + local.get $0 + i32.store + local.get $1 + local.get $2 + local.get $0 + i32.load + call_indirect (type $0) + local.set $3 + global.get $~lib/memory/__stack_pointer + i32.const 4 + i32.add + global.set $~lib/memory/__stack_pointer + local.get $3 + ) +) diff --git a/tests/compiler/tuple-type.ts b/tests/compiler/tuple-type.ts index 8cf62aeb86..8e8f771e94 100644 --- a/tests/compiler/tuple-type.ts +++ b/tests/compiler/tuple-type.ts @@ -1 +1,52 @@ -// nothing yet just getting the parser working first +export function pair(a: i32, b: i64): readonly [i32, i64] { + return [a, b]; +} + +export function floats(a: f32, b: f64): readonly [f32, f64] { + return [a, b]; +} + +export function forward(a: i32, b: i64): readonly [i32, i64] { + return pair(a, b); +} + +export function named(a: i32, b: i64): readonly [lo: i32, hi: i64] { + return [a, b]; +} + +type PairFn = (a: i32, b: i64) => readonly [i32, i64]; + +export function indirect(fn: PairFn, a: i32, b: i64): readonly [i32, i64] { + return fn(a, b); +} + +export function dropCall(a: i32, b: i64): void { + pair(a, b); +} + +// The callee must be evaluated before side-effecting arguments, while the +// enclosing block must preserve both results of the indirect call. +let order: i32 = 0; + +function choosePair(): PairFn { + order = order * 10 + 1; + return pair; +} + +function argument(): i32 { + order = order * 10 + 2; + return 7; +} + +export function indirectOrder(): readonly [i32, i64] { + order = 0; + return choosePair()(argument(), 9); +} + +export function evaluationOrder(): i32 { + return order; +} + +export function stackPointer(): usize { + return __stack_pointer; +} diff --git a/tests/parser/tuple.ts.fixture.ts b/tests/parser/tuple.ts.fixture.ts index bf461f898e..cc74b92ee7 100644 --- a/tests/parser/tuple.ts.fixture.ts +++ b/tests/parser/tuple.ts.fixture.ts @@ -37,16 +37,16 @@ function func2(x: [i32, i32]): [i32, i32] { function func3(x: [i32, [i32, i32]], y: i32): [i32, [i32, i32]] { return x; } -function func4(x: [i32, string]): [void] { +function func4(x: readonly [i32, string]): [void] { return [void(0)]; } -function func5(x: [Array, Array]): [i32] { +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: [[Array], [string]]): [[Array], [string]] { +function func7(x: readonly [[Array], [string]]): readonly [[Array], [string]] { return x; } function func8(x: [left: i32, right: i32]): [first: i32, second: i32] { @@ -61,7 +61,7 @@ function func10(x: [string | null, i32] | null): [string | null, i32] | null { type type0 = []; type type1 = [i32, i32]; type type2 = [i32, [i32, i32]]; -type type3 = [i32, string]; +type type3 = readonly [i32, string]; type type4 = [i32, i32] | null; type type5 = [[i32, i32], [i32, i32]]; type type6 = [Array, Array, T];