From 4d8f2d13eb6f4c9e18196e608d47283e1176e8b0 Mon Sep 17 00:00:00 2001 From: Ken Gorab Date: Sat, 2 Nov 2024 22:06:48 -0400 Subject: [PATCH] Try-else expressions (#481) Add an optional else-clause to try expressions which can transform the possible resulting error value of a try expression into a new value. The type of this value must match the success type of the try expression; it can also return a new value. --- projects/compiler/example.abra | 11 +- projects/compiler/src/compiler.abra | 60 +- projects/compiler/src/parser.abra | 23 +- projects/compiler/src/test_utils.abra | 22 +- projects/compiler/src/typechecker.abra | 86 +- .../compiler/src/typechecker_test_utils.abra | 34 +- projects/compiler/test/compiler/try.abra | 170 ++++ projects/compiler/test/parser/try.abra | 10 +- projects/compiler/test/parser/try.out.json | 421 ++++++++ .../test/parser/try_else_error_eof.abra | 1 + .../test/parser/try_else_error_eof.out | 4 + projects/compiler/test/run-tests.js | 8 + .../typechecker/if/error_empty_else_block.out | 2 +- .../typechecker/if/error_empty_if_block.out | 2 +- .../typechecker/if/error_no_else_block.out | 2 +- .../try/error_else_bad_destructuring.abra | 7 + .../try/error_else_bad_destructuring.out | 5 + .../try/error_else_block_empty.abra | 7 + .../try/error_else_block_empty.out | 5 + .../error_else_return_type_mismatch.1.abra | 7 + .../try/error_else_return_type_mismatch.1.out | 6 + .../error_else_return_type_mismatch.2.abra | 7 + .../try/error_else_return_type_mismatch.2.out | 6 + .../try/error_else_type_mismatch.abra | 7 + .../try/error_else_type_mismatch.out | 6 + .../compiler/test/typechecker/try/try.3.abra | 21 + .../test/typechecker/try/try.3.out.json | 904 ++++++++++++++++++ .../compiler/test/typechecker/try/try.4.abra | 13 + .../test/typechecker/try/try.4.out.json | 800 ++++++++++++++++ 29 files changed, 2620 insertions(+), 37 deletions(-) create mode 100644 projects/compiler/test/parser/try_else_error_eof.abra create mode 100644 projects/compiler/test/parser/try_else_error_eof.out create mode 100644 projects/compiler/test/typechecker/try/error_else_bad_destructuring.abra create mode 100644 projects/compiler/test/typechecker/try/error_else_bad_destructuring.out create mode 100644 projects/compiler/test/typechecker/try/error_else_block_empty.abra create mode 100644 projects/compiler/test/typechecker/try/error_else_block_empty.out create mode 100644 projects/compiler/test/typechecker/try/error_else_return_type_mismatch.1.abra create mode 100644 projects/compiler/test/typechecker/try/error_else_return_type_mismatch.1.out create mode 100644 projects/compiler/test/typechecker/try/error_else_return_type_mismatch.2.abra create mode 100644 projects/compiler/test/typechecker/try/error_else_return_type_mismatch.2.out create mode 100644 projects/compiler/test/typechecker/try/error_else_type_mismatch.abra create mode 100644 projects/compiler/test/typechecker/try/error_else_type_mismatch.out create mode 100644 projects/compiler/test/typechecker/try/try.3.abra create mode 100644 projects/compiler/test/typechecker/try/try.3.out.json create mode 100644 projects/compiler/test/typechecker/try/try.4.abra create mode 100644 projects/compiler/test/typechecker/try/try.4.out.json diff --git a/projects/compiler/example.abra b/projects/compiler/example.abra index 8726797c..75d0886d 100644 --- a/projects/compiler/example.abra +++ b/projects/compiler/example.abra @@ -1,10 +1,15 @@ func ok(): Result = Ok(123) func err(): Result = Err("foo") -func f1(): Result { - val x = try ok() +func f1(): Result { + var acc = 0 + while acc < 10 { + val x = try err() else break + println(x) + acc += x + } - Ok(x + 1) + Ok(acc) } /// Expect: Result.Ok(value: 124) diff --git a/projects/compiler/src/compiler.abra b/projects/compiler/src/compiler.abra index 6571f38b..9799e475 100644 --- a/projects/compiler/src/compiler.abra +++ b/projects/compiler/src/compiler.abra @@ -1742,22 +1742,64 @@ export type Compiler { val res = try self._compileMatch(node, isStatement, expr, cases) if res |res| Ok(res) else return unreachable("match expression needs a resulting value", node.token.position) } - TypedAstNodeKind.Try(expr) => { + TypedAstNodeKind.Try(expr, elseClause) => { val exprVal = try self._compileExpression(expr) val isErr = try self._emitResultValueIsOkVariant(exprVal, negate: true) val labelIsErr = self._currentFn.block.addLabel("isErr") val labelCont = self._currentFn.block.addLabel("cont") - self._currentFn.block.buildJnz(isErr, labelIsErr, labelCont) - self._currentFn.block.registerLabel(labelIsErr) - self._currentFn.block.buildReturn(Some(exprVal)) + if elseClause |clause| { + val phiCases: (Label, Value)[] = [] - self._currentFn.block.registerLabel(labelCont) - val okValTy = try self._getQbeTypeForTypeExpect(node.ty, "unacceptable type", None) - val okValue = try self._emitResultValueGetValue(okValTy, exprVal) + val labelIsOk = self._currentFn.block.addLabel("isOk") + self._currentFn.block.buildJnz(isErr, labelIsErr, labelIsOk) + + self._currentFn.block.registerLabel(labelIsErr) + if clause.pattern |(bindingPattern, vars)| { + val variables = vars.keyBy(v => v.label.name) + + val errorQbeType = try self._getQbeTypeForTypeExpect(clause.errorType, "unacceptable type", None) + val bindingVal = try self._emitOptValueGetValue(errorQbeType, exprVal) + try self._compileBindingPattern(bindingPattern, variables, Some(bindingVal)) + } + for node, idx in clause.block { + val res = try self._compileStatement(node) + if idx == clause.block.length - 1 { + if res |res| { + val label = self._currentFn.block.currentLabel + phiCases.push((label, res)) + } else if !clause.terminator { + return unreachable("last statement in try-else block has no value and is not a terminator", node.token.position) + } + } + } + if !clause.terminator { + self._currentFn.block.buildJmp(labelCont) + } + + self._currentFn.block.registerLabel(labelIsOk) + val okQbeType = try self._getQbeTypeForTypeExpect(node.ty, "unacceptable type", None) + val okVal = try self._emitOptValueGetValue(okQbeType, exprVal) + val label = self._currentFn.block.currentLabel + phiCases.push((label, okVal)) + self._currentFn.block.buildJmp(labelCont) + + self._currentFn.block.registerLabel(labelCont) + val res = match self._currentFn.block.buildPhi(phiCases) { Ok(v) => v, Err(e) => return qbeError(e) } + Ok(res) + } else { + self._currentFn.block.buildJnz(isErr, labelIsErr, labelCont) + + self._currentFn.block.registerLabel(labelIsErr) + self._currentFn.block.buildReturn(Some(exprVal)) + + self._currentFn.block.registerLabel(labelCont) + val okValTy = try self._getQbeTypeForTypeExpect(node.ty, "unacceptable type", None) + val okValue = try self._emitResultValueGetValue(okValTy, exprVal) - Ok(okValue) + Ok(okValue) + } } _ => unreachable("node must be a statement", node.token.position) } @@ -4274,7 +4316,7 @@ export type Compiler { // in place of the type. 16 chars seems like a good balance between long enough that there won't be any conflicts, but also // short enough that there's enough room for this type to be used as a generic in other types without making that type's or // function's identifier too long. - if name.length >= 80 { + if name.length >= 70 { val alias = if self._aliasedTypeNames[name] |alias| alias else { val replacement = String.random(16) self._aliasedTypeNames[name] = replacement diff --git a/projects/compiler/src/parser.abra b/projects/compiler/src/parser.abra index b7a51a36..ed4bfdb2 100644 --- a/projects/compiler/src/parser.abra +++ b/projects/compiler/src/parser.abra @@ -268,7 +268,7 @@ export enum AstNodeKind { Assignment(expr: AstNode, op: AssignOp, mode: AssignmentMode) If(condition: AstNode, conditionBinding: BindingPattern?, ifBlock: AstNode[], elseBlock: AstNode[]?) Match(expr: AstNode, cases: MatchCase[]) - Try(expr: AstNode) + Try(expr: AstNode, elseClause: (Token, BindingPattern?, AstNode[])?) While(condition: AstNode, conditionBinding: BindingPattern?, block: AstNode[]) For(itemPattern: BindingPattern, indexPattern: BindingPattern?, iterator: AstNode, block: AstNode[]) BindingDeclaration(value: BindingDeclarationNode) @@ -1476,7 +1476,26 @@ export type Parser { val token = try self._expectNext() val expr = try self._parseExpression() - Ok(AstNode(token: token, kind: AstNodeKind.Try(expr))) + val elseClause = if self._peek() |token| { + if token.kind == TokenKind.Else { + val elseToken = try self._expectNextTokenKind(TokenKind.Else) + val nextToken = try self._expectPeek() + val binding = if nextToken.kind == TokenKind.Pipe { + self._advance() // consume opening '|' token + val pat = try self._parseBindingPattern() + try self._expectNextTokenKind(TokenKind.Pipe) + Some(pat) + } else { + None + } + + val elseBlock = try self._parseBlockOrSingleExpression(allowTerminators: true) + + Some((elseToken, binding, elseBlock)) + } else None + } else None + + Ok(AstNode(token: token, kind: AstNodeKind.Try(expr, elseClause))) } func _parseWhileLoop(self): Result { diff --git a/projects/compiler/src/test_utils.abra b/projects/compiler/src/test_utils.abra index b804d89a..75cbaf33 100644 --- a/projects/compiler/src/test_utils.abra +++ b/projects/compiler/src/test_utils.abra @@ -719,10 +719,30 @@ func printAstNodeKindAsJson(kind: AstNodeKind, indentLevelStart: Int, currentInd println("$fieldsIndent]") } } - AstNodeKind.Try(expr) => { + AstNodeKind.Try(expr, elseClause) => { println("$fieldsIndent\"name\": \"try\",") print("$fieldsIndent\"expr\": ") printAstNodeAsJson(expr, 0, currentIndentLevel + 1) + if elseClause |(_, bindingPattern, block)| { + if bindingPattern |pat| { + print(",\n$fieldsIndent\"elseBindingPattern\": ") + printBindingPatternAsJson(pat, 0, currentIndentLevel + 1) + } else { + print(",\n$fieldsIndent\"elseBindingPattern\": null") + } + + if block.isEmpty() { + println(",\n$fieldsIndent\"elseBlock\": []") + } else { + println(",\n$fieldsIndent\"elseBlock\": [") + for node, idx in block { + printAstNodeAsJson(node, currentIndentLevel + 2, currentIndentLevel + 2) + val comma = if idx != block.length - 1 "," else "" + println("$comma") + } + print("$fieldsIndent]") + } + } println() } AstNodeKind.While(condition, conditionBinding, block) => { diff --git a/projects/compiler/src/typechecker.abra b/projects/compiler/src/typechecker.abra index 081310d1..8282a0be 100644 --- a/projects/compiler/src/typechecker.abra +++ b/projects/compiler/src/typechecker.abra @@ -135,6 +135,7 @@ export enum ScopeKind { While For Type + Try } export type Scope { @@ -739,6 +740,13 @@ export type TypedMatchCase { terminator: Terminator? } +type TypedTryElseClause { + pattern: (BindingPattern, Variable[])? + errorType: Type + block: TypedAstNode[] + terminator: Terminator? +} + export enum TypedAstNodeKind { Literal(value: LiteralAstNode) StringInterpolation(exprs: TypedAstNode[]) @@ -757,7 +765,7 @@ export enum TypedAstNodeKind { Assignment(mode: TypedAssignmentMode, op: AssignOp, expr: TypedAstNode) If(isStatement: Bool, typedCondition: TypedAstNode, conditionBinding: (BindingPattern, Variable[])?, typedIfBlock: TypedAstNode[], ifBlockTerminator: Terminator?, typedElseBlock: TypedAstNode[], elseBlockTerminator: Terminator?) Match(isStatement: Bool, expr: TypedAstNode, cases: TypedMatchCase[]) - Try(typedExpr: TypedAstNode) + Try(typedExpr: TypedAstNode, typedElseClause: TypedTryElseClause?) While(typedCondition: TypedAstNode, conditionBinding: (BindingPattern, Variable[])?, block: TypedAstNode[], terminator: Terminator?) For(typedIterator: TypedAstNode, itemBinding: (BindingPattern, Variable[]), indexBinding: Variable?, block: TypedAstNode[]) BindingDeclaration(node: TypedBindingDeclarationNode) @@ -980,8 +988,8 @@ type TypeError { } } } - TypeErrorKind.MissingRequiredIfExprBlock(clause, missing) => { - lines.push("Incomplete if-expression") + TypeErrorKind.MissingRequiredBlock(exprKind, clause, missing) => { + lines.push("Incomplete $exprKind expression") lines.push(self._getCursorLine(self.position, contents)) if missing { lines.push("The $clause-block must exist and contain a value") @@ -1409,7 +1417,7 @@ enum TypeErrorKind { IllegalNonConstantEnumVariant IllegalValueType(ty: Type, purpose: String) IllegalControlFlowType(ty: Type, purpose: String) - MissingRequiredIfExprBlock(clause: String, missing: Bool) + MissingRequiredBlock(exprKind: String, clause: String, missing: Bool) InvalidParamPosition(purpose: String) DuplicateParameter(name: String) InvalidVarargType(ty: Type) @@ -3360,6 +3368,10 @@ export type Typechecker { scope = sc.parent continue } + ScopeKind.Try => { + scope = sc.parent + continue + } ScopeKind.While => true ScopeKind.For => true } @@ -3473,7 +3485,7 @@ export type Typechecker { self.currentScope.terminator = Terminator.combine(ifBlockTerminator, elseBlockTerminator) val ty = if !isStatement { - val t = if ifType |ifType| { + if ifType |ifType| { if typedElseBlock[-1] |lastElseNode| { val elseType = lastElseNode.ty @@ -3488,15 +3500,13 @@ export type Typechecker { return Err(TypeError(position: lastElseNode.token.position, kind: TypeErrorKind.TypeMismatch([ifType], elseType))) } - val t = if ifType.kind == TypeKind.Never { elseType } else { ifType } - t + if ifType.kind == TypeKind.Never { elseType } else { ifType } } else { - return Err(TypeError(position: token.position, kind: TypeErrorKind.MissingRequiredIfExprBlock(clause: "else", missing: !hasElseBlock))) + return Err(TypeError(position: token.position, kind: TypeErrorKind.MissingRequiredBlock(exprKind: "if-else", clause: "else", missing: !hasElseBlock))) } } else { - return Err(TypeError(position: token.position, kind: TypeErrorKind.MissingRequiredIfExprBlock(clause: "if", missing: false))) + return Err(TypeError(position: token.position, kind: TypeErrorKind.MissingRequiredBlock(exprKind: "if-else", clause: "if", missing: false))) } - t } else { Type(kind: TypeKind.PrimitiveUnit) } @@ -3789,7 +3799,7 @@ export type Typechecker { AstNodeKind.Lambda(value) => self._typecheckLambda(token, value, typeHint) AstNodeKind.If(condition, conditionBinding, ifBlock, elseBlock) => self._typecheckIf(token, condition, conditionBinding, ifBlock, elseBlock, typeHint) AstNodeKind.Match(subject, cases) => self._typecheckMatch(token, subject, cases, typeHint) - AstNodeKind.Try(expr) => self._typecheckTry(token, expr, typeHint) + AstNodeKind.Try(expr, elseClause) => self._typecheckTry(token, expr, elseClause, typeHint) _ => unreachable(node.token.position, "all other node types should have already been handled") } @@ -4881,7 +4891,7 @@ export type Typechecker { Ok(TypedAstNode(token: token, ty: lambdaTy, kind: TypedAstNodeKind.Lambda(fn, typeHint))) } - func _typecheckTry(self, token: Token, expr: AstNode, typeHint: Type?): Result { + func _typecheckTry(self, token: Token, expr: AstNode, elseClause: (Token, BindingPattern?, AstNode[])?, typeHint: Type?): Result { // TODO: support top-level try (the error case would just exit the program) val currentFn = if self.currentFunction |f| f else return Err(TypeError(position: token.position, kind: TypeErrorKind.InvalidTryLocation(InvalidTryLocationReason.NotWithinFunction))) @@ -4901,11 +4911,59 @@ export type Typechecker { // TODO: other Try-able types val (tryValType, tryErrType) = if self._typeIsResult(typedExpr.ty) |t| t else return Err(TypeError(position: typedExpr.token.position, kind: TypeErrorKind.InvalidTryTarget(typedExpr.ty))) - if !self._typeSatisfiesRequired(ty: retErrType, required: tryErrType) { + val typedElseClause = if elseClause |(elseToken, bindingPattern, elseBlock)| { + val isStatement = if typeHint |hint| hint.kind == TypeKind.PrimitiveUnit else false + + val prevScope = self.currentScope + self.currentScope = self.currentScope.makeChild("try_else", ScopeKind.Try) + + val elseBindingPattern = if bindingPattern |pat| { + val variables = try self._typecheckBindingPattern(false, pat, tryErrType) + Some((pat, variables)) + } else { + None + } + + val typedNodes: TypedAstNode[] = [] + for node, idx in elseBlock { + if self.currentScope.terminator return Err(TypeError(position: node.token.position, kind: TypeErrorKind.UnreachableCode)) + + val typedNode = if idx == elseBlock.length - 1 && !isStatement { + try self._typecheckExpressionOrTerminator(node, typeHint) + } else { + try self._typecheckStatement(node, None) + } + typedNodes.push(typedNode) + } + val terminator = self.currentScope.terminator + + if typedNodes[-1] |lastElseNode| { + val elseType = lastElseNode.ty + + if tryValType.hasUnfilledHoles() { + tryValType.tryFillHoles(elseType) + } + if elseType.hasUnfilledHoles() { + elseType.tryFillHoles(tryValType) + } + + if !self._typeSatisfiesRequired(ty: elseType, required: tryValType) { + return Err(TypeError(position: lastElseNode.token.position, kind: TypeErrorKind.TypeMismatch([tryValType], elseType))) + } + } else { + return Err(TypeError(position: elseToken.position, kind: TypeErrorKind.MissingRequiredBlock(exprKind: "try-else", clause: "else", missing: false))) + } + + self.currentScope = prevScope + + Some(TypedTryElseClause(pattern: elseBindingPattern, errorType: tryErrType, block: typedNodes, terminator: terminator)) + } else if !self._typeSatisfiesRequired(ty: retErrType, required: tryErrType) { return Err(TypeError(position: token.position, kind: TypeErrorKind.TryReturnTypeMismatch(fnLabel: currentFn.label, tryType: typedExpr.ty, tryErrType: tryErrType, retErrType: retErrType))) + } else { + None } - Ok(TypedAstNode(token: token, ty: tryValType, kind: TypedAstNodeKind.Try(typedExpr))) + Ok(TypedAstNode(token: token, ty: tryValType, kind: TypedAstNodeKind.Try(typedExpr, typedElseClause))) } } diff --git a/projects/compiler/src/typechecker_test_utils.abra b/projects/compiler/src/typechecker_test_utils.abra index 6b015db4..f071705f 100644 --- a/projects/compiler/src/typechecker_test_utils.abra +++ b/projects/compiler/src/typechecker_test_utils.abra @@ -673,11 +673,36 @@ export type Jsonifier { }) println() } - TypedAstNodeKind.Try(expr) => { + TypedAstNodeKind.Try(expr, elseClause) => { self.println("\"kind\": \"try\",") self.print("\"expr\": ") self.printNode(expr) + if elseClause |elseClause| { + println(",") + self.print("\"elseBindingPattern\": ") + self.opt(elseClause.pattern, _pair => { + val (pat, vars) = _pair + println("{") + self.indentInc() + + self.print("\"bindingPattern\": ") + printBindingPatternAsJson(pat, 0, self.indentLevel) + println(",") + + self.print("\"variables\": ") + self.array(vars, v => self.printVariable(v)) + println() + + self.indentDec() + self.print("}") + }) + + println(",") + + self.print("\"elseBlock\": ") + self.array(elseClause.block, n => self.printNode(n)) + } println() } @@ -690,16 +715,17 @@ export type Jsonifier { println(",") self.print("\"conditionBindingPattern\": ") - self.opt(conditionBindingPattern, pair => { + self.opt(conditionBindingPattern, _pair => { + val (pat, vars) = _pair println("{") self.indentInc() self.print("\"bindingPattern\": ") - printBindingPatternAsJson(pair[0], 0, self.indentLevel) + printBindingPatternAsJson(pat, 0, self.indentLevel) println(",") self.print("\"variables\": ") - self.array(pair[1], v => self.printVariable(v)) + self.array(vars, v => self.printVariable(v)) println() self.indentDec() diff --git a/projects/compiler/test/compiler/try.abra b/projects/compiler/test/compiler/try.abra index b8f8f79e..d1d471e7 100644 --- a/projects/compiler/test/compiler/try.abra +++ b/projects/compiler/test/compiler/try.abra @@ -35,3 +35,173 @@ func f5(): Result { } /// Expect: Result.Err(error: "foo") println(f5()) + +// Testing else-clause with terminators + +func f6(): Result { + var acc = 0 + while acc < 10 { + val x = try ok() else { + acc = 199 + continue + } + acc += x + } + + Ok(acc) +} + +/// Expect: Result.Ok(value: 123) +println(f6()) + +func f7(): Result { + var acc = 0 + while acc < 10 { + val x = try ok() else break + acc += x + } + + Ok(acc) +} + +/// Expect: Result.Ok(value: 123) +println(f7()) + + +func f8(): Result { + var acc = 0 + while acc < 10 { + val x = try err() else { + acc = 199 + continue + } + acc += x + } + + Ok(acc) +} + +/// Expect: Result.Ok(value: 199) +println(f8()) + + +func f9(): Result { + var acc = 0 + while acc < 10 { + val x = try err() else break + acc += x + } + + Ok(acc) +} + +/// Expect: Result.Ok(value: 0) +println(f9()) + +func f10(): Result { + var acc = 0 + while acc < 10 { + val x = try ok() else return Ok(12) + acc += x + } + + Ok(acc) +} + +/// Expect: Result.Ok(value: 123) +println(f10()) + +func f11(): Result { + var acc = 0 + while acc < 10 { + val x = try ok() else return Err(12) + acc += x + } + + Ok(acc) +} + +/// Expect: Result.Ok(value: 123) +println(f11()) + +func f12(): Result { + var acc = 0 + while acc < 10 { + val x = try err() else return Ok(12) + acc += x + } + + Ok(acc) +} + +/// Expect: Result.Ok(value: 12) +println(f12()) + +func f13(): Result { + var acc = 0 + while acc < 10 { + val x = try err() else return Err(12) + acc += x + } + + Ok(acc) +} + +/// Expect: Result.Err(error: 12) +println(f13()) + +func f14(): Result { + val x = try ok() else return Ok(12) + + Ok(x) +} + +/// Expect: Result.Ok(value: 123) +println(f14()) + +func f15(): Result { + val x = try ok() else return Err(12) + + Ok(x) +} + +/// Expect: Result.Ok(value: 123) +println(f15()) + +func f16(): Result { + val x = try err() else return Ok(12) + + Ok(x) +} + +/// Expect: Result.Ok(value: 12) +println(f16()) + +func f17(): Result { + val x = try err() else return Err(12) + + Ok(x) +} + +/// Expect: Result.Err(error: 12) +println(f17()) + +// Testing else-clause without terminators + +func f18(): Result { + val x = try ok() else 7 + + Ok(x) +} + +/// Expect: Result.Ok(value: 123) +println(f18()) + +func f19(): Result { + val x = try err() else 7 + + Ok(x) +} + +/// Expect: Result.Ok(value: 7) +println(f19()) diff --git a/projects/compiler/test/parser/try.abra b/projects/compiler/test/parser/try.abra index f450e707..2a352d1e 100644 --- a/projects/compiler/test/parser/try.abra +++ b/projects/compiler/test/parser/try.abra @@ -1,2 +1,10 @@ val x = try foo() -val y = try foo().abc \ No newline at end of file +val y = try foo().abc + +val a = try foo() else bar +val b = try foo() else |e| bar(e) +val c = try foo() else |e| { + val x = e + 1 + bar(x) +} +val d = try foo() else |e| return e \ No newline at end of file diff --git a/projects/compiler/test/parser/try.out.json b/projects/compiler/test/parser/try.out.json index c128558d..a036aebe 100644 --- a/projects/compiler/test/parser/try.out.json +++ b/projects/compiler/test/parser/try.out.json @@ -133,6 +133,427 @@ } } } + }, + { + "token": { + "position": [4, 1], + "kind": { + "name": "Val" + } + }, + "kind": { + "name": "bindingDecl", + "decorators": [], + "exportToken": null, + "bindingPattern": { + "kind": "variable", + "label": { "name": "a", "position": [4, 5] } + }, + "typeAnnotation": null, + "expr": { + "token": { + "position": [4, 9], + "kind": { + "name": "Try" + } + }, + "kind": { + "name": "try", + "expr": { + "token": { + "position": [4, 16], + "kind": { + "name": "LParen" + } + }, + "kind": { + "name": "invocation", + "invokee": { + "token": { + "position": [4, 13], + "kind": { + "name": "Ident", + "value": "foo" + } + }, + "kind": { + "name": "identifier", + "ident": "foo" + } + }, + "typeArguments": [], + "arguments": [] + } + }, + "elseBindingPattern": null, + "elseBlock": [ + { + "token": { + "position": [4, 24], + "kind": { + "name": "Ident", + "value": "bar" + } + }, + "kind": { + "name": "identifier", + "ident": "bar" + } + } + ] + } + } + } + }, + { + "token": { + "position": [5, 1], + "kind": { + "name": "Val" + } + }, + "kind": { + "name": "bindingDecl", + "decorators": [], + "exportToken": null, + "bindingPattern": { + "kind": "variable", + "label": { "name": "b", "position": [5, 5] } + }, + "typeAnnotation": null, + "expr": { + "token": { + "position": [5, 9], + "kind": { + "name": "Try" + } + }, + "kind": { + "name": "try", + "expr": { + "token": { + "position": [5, 16], + "kind": { + "name": "LParen" + } + }, + "kind": { + "name": "invocation", + "invokee": { + "token": { + "position": [5, 13], + "kind": { + "name": "Ident", + "value": "foo" + } + }, + "kind": { + "name": "identifier", + "ident": "foo" + } + }, + "typeArguments": [], + "arguments": [] + } + }, + "elseBindingPattern": { + "kind": "variable", + "label": { "name": "e", "position": [5, 25] } + }, + "elseBlock": [ + { + "token": { + "position": [5, 31], + "kind": { + "name": "LParen" + } + }, + "kind": { + "name": "invocation", + "invokee": { + "token": { + "position": [5, 28], + "kind": { + "name": "Ident", + "value": "bar" + } + }, + "kind": { + "name": "identifier", + "ident": "bar" + } + }, + "typeArguments": [], + "arguments": [ + { + "label": null, + "value": { + "token": { + "position": [5, 32], + "kind": { + "name": "Ident", + "value": "e" + } + }, + "kind": { + "name": "identifier", + "ident": "e" + } + } + } + ] + } + } + ] + } + } + } + }, + { + "token": { + "position": [6, 1], + "kind": { + "name": "Val" + } + }, + "kind": { + "name": "bindingDecl", + "decorators": [], + "exportToken": null, + "bindingPattern": { + "kind": "variable", + "label": { "name": "c", "position": [6, 5] } + }, + "typeAnnotation": null, + "expr": { + "token": { + "position": [6, 9], + "kind": { + "name": "Try" + } + }, + "kind": { + "name": "try", + "expr": { + "token": { + "position": [6, 16], + "kind": { + "name": "LParen" + } + }, + "kind": { + "name": "invocation", + "invokee": { + "token": { + "position": [6, 13], + "kind": { + "name": "Ident", + "value": "foo" + } + }, + "kind": { + "name": "identifier", + "ident": "foo" + } + }, + "typeArguments": [], + "arguments": [] + } + }, + "elseBindingPattern": { + "kind": "variable", + "label": { "name": "e", "position": [6, 25] } + }, + "elseBlock": [ + { + "token": { + "position": [7, 3], + "kind": { + "name": "Val" + } + }, + "kind": { + "name": "bindingDecl", + "decorators": [], + "exportToken": null, + "bindingPattern": { + "kind": "variable", + "label": { "name": "x", "position": [7, 7] } + }, + "typeAnnotation": null, + "expr": { + "token": { + "position": [7, 13], + "kind": { + "name": "Plus" + } + }, + "kind": { + "name": "binary", + "op": "BinaryOp.Add", + "left": { + "token": { + "position": [7, 11], + "kind": { + "name": "Ident", + "value": "e" + } + }, + "kind": { + "name": "identifier", + "ident": "e" + } + }, + "right": { + "token": { + "position": [7, 15], + "kind": { + "name": "Int", + "value": 1 + } + }, + "kind": { + "name": "literal", + "type": "int", + "value": 1 + } + } + } + } + } + }, + { + "token": { + "position": [8, 6], + "kind": { + "name": "LParen" + } + }, + "kind": { + "name": "invocation", + "invokee": { + "token": { + "position": [8, 3], + "kind": { + "name": "Ident", + "value": "bar" + } + }, + "kind": { + "name": "identifier", + "ident": "bar" + } + }, + "typeArguments": [], + "arguments": [ + { + "label": null, + "value": { + "token": { + "position": [8, 7], + "kind": { + "name": "Ident", + "value": "x" + } + }, + "kind": { + "name": "identifier", + "ident": "x" + } + } + } + ] + } + } + ] + } + } + } + }, + { + "token": { + "position": [10, 1], + "kind": { + "name": "Val" + } + }, + "kind": { + "name": "bindingDecl", + "decorators": [], + "exportToken": null, + "bindingPattern": { + "kind": "variable", + "label": { "name": "d", "position": [10, 5] } + }, + "typeAnnotation": null, + "expr": { + "token": { + "position": [10, 9], + "kind": { + "name": "Try" + } + }, + "kind": { + "name": "try", + "expr": { + "token": { + "position": [10, 16], + "kind": { + "name": "LParen" + } + }, + "kind": { + "name": "invocation", + "invokee": { + "token": { + "position": [10, 13], + "kind": { + "name": "Ident", + "value": "foo" + } + }, + "kind": { + "name": "identifier", + "ident": "foo" + } + }, + "typeArguments": [], + "arguments": [] + } + }, + "elseBindingPattern": { + "kind": "variable", + "label": { "name": "e", "position": [10, 25] } + }, + "elseBlock": [ + { + "token": { + "position": [10, 28], + "kind": { + "name": "Return" + } + }, + "kind": { + "name": "return", + "expr": { + "token": { + "position": [10, 35], + "kind": { + "name": "Ident", + "value": "e" + } + }, + "kind": { + "name": "identifier", + "ident": "e" + } + } + } + } + ] + } + } + } } ] } diff --git a/projects/compiler/test/parser/try_else_error_eof.abra b/projects/compiler/test/parser/try_else_error_eof.abra new file mode 100644 index 00000000..1f9ebbad --- /dev/null +++ b/projects/compiler/test/parser/try_else_error_eof.abra @@ -0,0 +1 @@ +val x = try foo else \ No newline at end of file diff --git a/projects/compiler/test/parser/try_else_error_eof.out b/projects/compiler/test/parser/try_else_error_eof.out new file mode 100644 index 00000000..d3d94dcd --- /dev/null +++ b/projects/compiler/test/parser/try_else_error_eof.out @@ -0,0 +1,4 @@ +Error at %FILE_NAME%:1:18 +Unexpected end of file: + | val x = try foo else + ^ \ No newline at end of file diff --git a/projects/compiler/test/run-tests.js b/projects/compiler/test/run-tests.js index 0c2406e5..e6a81b0a 100644 --- a/projects/compiler/test/run-tests.js +++ b/projects/compiler/test/run-tests.js @@ -109,6 +109,7 @@ const PARSER_TESTS = [ { test: "parser/try.abra", assertions: "parser/try.out.json" }, { test: "parser/try_error_eof.abra", assertions: "parser/try_error_eof.out" }, { test: "parser/try_error_invalid_token.abra", assertions: "parser/try_error_invalid_token.out" }, + { test: "parser/try_else_error_eof.abra", assertions: "parser/try_else_error_eof.out" }, // Assignment { test: "parser/assignment.abra", assertions: "parser/assignment.out.json" }, { test: "parser/assignment_error_as_expr.abra", assertions: "parser/assignment_error_as_expr.out" }, @@ -531,12 +532,19 @@ const TYPECHECKER_TESTS = [ // Try { test: "typechecker/try/try.1.abra", assertions: "typechecker/try/try.1.out.json" }, { test: "typechecker/try/try.2.abra", assertions: "typechecker/try/try.2.out.json" }, + { test: "typechecker/try/try.3.abra", assertions: "typechecker/try/try.3.out.json" }, + { test: "typechecker/try/try.4.abra", assertions: "typechecker/try/try.4.out.json" }, { test: "typechecker/try/error_bad_location.1.abra", assertions: "typechecker/try/error_bad_location.1.out" }, { test: "typechecker/try/error_bad_location.2.abra", assertions: "typechecker/try/error_bad_location.2.out" }, { test: "typechecker/try/error_bad_return_type.abra", assertions: "typechecker/try/error_bad_return_type.out" }, { test: "typechecker/try/error_return_type_err_mismatch.abra", assertions: "typechecker/try/error_return_type_err_mismatch.out" }, { test: "typechecker/try/error_return_type_err_mismatch_generic.abra", assertions: "typechecker/try/error_return_type_err_mismatch_generic.out" }, { test: "typechecker/try/error_untryable_type.abra", assertions: "typechecker/try/error_untryable_type.out" }, + { test: "typechecker/try/error_else_bad_destructuring.abra", assertions: "typechecker/try/error_else_bad_destructuring.out" }, + { test: "typechecker/try/error_else_block_empty.abra", assertions: "typechecker/try/error_else_block_empty.out" }, + { test: "typechecker/try/error_else_return_type_mismatch.1.abra", assertions: "typechecker/try/error_else_return_type_mismatch.1.out" }, + { test: "typechecker/try/error_else_return_type_mismatch.2.abra", assertions: "typechecker/try/error_else_return_type_mismatch.2.out" }, + { test: "typechecker/try/error_else_type_mismatch.abra", assertions: "typechecker/try/error_else_type_mismatch.out" }, // Type identifiers { test: "typechecker/typeidentifier/error_typearg_unknown.abra", assertions: "typechecker/typeidentifier/error_typearg_unknown.out" }, diff --git a/projects/compiler/test/typechecker/if/error_empty_else_block.out b/projects/compiler/test/typechecker/if/error_empty_else_block.out index b4f37a37..5e5908a2 100644 --- a/projects/compiler/test/typechecker/if/error_empty_else_block.out +++ b/projects/compiler/test/typechecker/if/error_empty_else_block.out @@ -1,5 +1,5 @@ Error at %FILE_NAME%:1:9 -Incomplete if-expression +Incomplete if-else expression | val a = if true 123 else { } ^ The else-block must contain a value diff --git a/projects/compiler/test/typechecker/if/error_empty_if_block.out b/projects/compiler/test/typechecker/if/error_empty_if_block.out index 35616e48..f5106a2b 100644 --- a/projects/compiler/test/typechecker/if/error_empty_if_block.out +++ b/projects/compiler/test/typechecker/if/error_empty_if_block.out @@ -1,5 +1,5 @@ Error at %FILE_NAME%:1:9 -Incomplete if-expression +Incomplete if-else expression | val a = if true { } else 456 ^ The if-block must contain a value diff --git a/projects/compiler/test/typechecker/if/error_no_else_block.out b/projects/compiler/test/typechecker/if/error_no_else_block.out index eb4dc452..435195a9 100644 --- a/projects/compiler/test/typechecker/if/error_no_else_block.out +++ b/projects/compiler/test/typechecker/if/error_no_else_block.out @@ -1,5 +1,5 @@ Error at %FILE_NAME%:1:9 -Incomplete if-expression +Incomplete if-else expression | val a = if true 123 ^ The else-block must exist and contain a value diff --git a/projects/compiler/test/typechecker/try/error_else_bad_destructuring.abra b/projects/compiler/test/typechecker/try/error_else_bad_destructuring.abra new file mode 100644 index 00000000..44580baf --- /dev/null +++ b/projects/compiler/test/typechecker/try/error_else_bad_destructuring.abra @@ -0,0 +1,7 @@ +func ok(): Result = Ok(123) + +func f1(): Result { + val x = try ok() else |(e1, e2)| {} + + Ok(x + 1) +} \ No newline at end of file diff --git a/projects/compiler/test/typechecker/try/error_else_bad_destructuring.out b/projects/compiler/test/typechecker/try/error_else_bad_destructuring.out new file mode 100644 index 00000000..b8b53f90 --- /dev/null +++ b/projects/compiler/test/typechecker/try/error_else_bad_destructuring.out @@ -0,0 +1,5 @@ +Error at %FILE_NAME%:4:26 +Invalid destructuring + | val x = try ok() else |(e1, e2)| {} + ^ +A value of type 'String' cannot be destructured as a tuple diff --git a/projects/compiler/test/typechecker/try/error_else_block_empty.abra b/projects/compiler/test/typechecker/try/error_else_block_empty.abra new file mode 100644 index 00000000..43cbfecc --- /dev/null +++ b/projects/compiler/test/typechecker/try/error_else_block_empty.abra @@ -0,0 +1,7 @@ +func ok(): Result = Ok(123) + +func f1(): Result { + val x = try ok() else |e| {} + + Ok(x + 1) +} \ No newline at end of file diff --git a/projects/compiler/test/typechecker/try/error_else_block_empty.out b/projects/compiler/test/typechecker/try/error_else_block_empty.out new file mode 100644 index 00000000..6b032dd4 --- /dev/null +++ b/projects/compiler/test/typechecker/try/error_else_block_empty.out @@ -0,0 +1,5 @@ +Error at %FILE_NAME%:4:20 +Incomplete try-else expression + | val x = try ok() else |e| {} + ^ +The else-block must contain a value diff --git a/projects/compiler/test/typechecker/try/error_else_return_type_mismatch.1.abra b/projects/compiler/test/typechecker/try/error_else_return_type_mismatch.1.abra new file mode 100644 index 00000000..73a5a7a9 --- /dev/null +++ b/projects/compiler/test/typechecker/try/error_else_return_type_mismatch.1.abra @@ -0,0 +1,7 @@ +func ok(): Result = Ok(123) + +func f1(): Result { + val x = try ok() else return Err("asdf") + + Ok(x + 1) +} \ No newline at end of file diff --git a/projects/compiler/test/typechecker/try/error_else_return_type_mismatch.1.out b/projects/compiler/test/typechecker/try/error_else_return_type_mismatch.1.out new file mode 100644 index 00000000..a753e281 --- /dev/null +++ b/projects/compiler/test/typechecker/try/error_else_return_type_mismatch.1.out @@ -0,0 +1,6 @@ +Error at %FILE_NAME%:4:36 +Type mismatch for parameter 'error' + | val x = try ok() else return Err("asdf") + ^ +Expected: Int +but instead found: String diff --git a/projects/compiler/test/typechecker/try/error_else_return_type_mismatch.2.abra b/projects/compiler/test/typechecker/try/error_else_return_type_mismatch.2.abra new file mode 100644 index 00000000..5a892651 --- /dev/null +++ b/projects/compiler/test/typechecker/try/error_else_return_type_mismatch.2.abra @@ -0,0 +1,7 @@ +func ok(): Result = Ok(123) + +func f1(): Result { + val x = try ok() else return ok() + + Ok(x + 1) +} \ No newline at end of file diff --git a/projects/compiler/test/typechecker/try/error_else_return_type_mismatch.2.out b/projects/compiler/test/typechecker/try/error_else_return_type_mismatch.2.out new file mode 100644 index 00000000..775bd2df --- /dev/null +++ b/projects/compiler/test/typechecker/try/error_else_return_type_mismatch.2.out @@ -0,0 +1,6 @@ +Error at %FILE_NAME%:4:34 +Return type mismatch for function 'f1' + | val x = try ok() else return ok() + ^ +Expected Result +but instead found Result diff --git a/projects/compiler/test/typechecker/try/error_else_type_mismatch.abra b/projects/compiler/test/typechecker/try/error_else_type_mismatch.abra new file mode 100644 index 00000000..0681115a --- /dev/null +++ b/projects/compiler/test/typechecker/try/error_else_type_mismatch.abra @@ -0,0 +1,7 @@ +func ok(): Result = Ok(123) + +func f1(): Result { + val x = try ok() else |e| e.isEmpty() + + Ok(x + 1) +} \ No newline at end of file diff --git a/projects/compiler/test/typechecker/try/error_else_type_mismatch.out b/projects/compiler/test/typechecker/try/error_else_type_mismatch.out new file mode 100644 index 00000000..850c838f --- /dev/null +++ b/projects/compiler/test/typechecker/try/error_else_type_mismatch.out @@ -0,0 +1,6 @@ +Error at %FILE_NAME%:4:38 +Type mismatch + | val x = try ok() else |e| e.isEmpty() + ^ +Expected: Int +but instead found: Bool diff --git a/projects/compiler/test/typechecker/try/try.3.abra b/projects/compiler/test/typechecker/try/try.3.abra new file mode 100644 index 00000000..f69df47e --- /dev/null +++ b/projects/compiler/test/typechecker/try/try.3.abra @@ -0,0 +1,21 @@ +func ok(): Result = Ok(123) + +func f1(): Result { + var a = 0 + while true { + val x = try ok() else break + a += x + } + + Ok(a) +} + +func f2(): Result { + var a = 0 + while true { + val x = try ok() else continue + a += x + } + + Ok(a) +} \ No newline at end of file diff --git a/projects/compiler/test/typechecker/try/try.3.out.json b/projects/compiler/test/typechecker/try/try.3.out.json new file mode 100644 index 00000000..9be564c3 --- /dev/null +++ b/projects/compiler/test/typechecker/try/try.3.out.json @@ -0,0 +1,904 @@ +{ + "id": 3, + "name": "%FILE_NAME%", + "code": [ + { + "token": { + "position": [1, 1], + "kind": { + "name": "Func" + } + }, + "type": { + "kind": "primitive", + "primitive": "Unit" + }, + "node": { + "kind": "functionDeclaration", + "function": { + "label": { "name": "ok", "position": [1, 6] }, + "scope": { + "name": "$root::module_3::ok", + "variables": [], + "functions": [], + "types": [] + }, + "kind": "FunctionKind.Standalone", + "typeParameters": [], + "parameters": [], + "returnType": { + "kind": "enumInstance", + "enum": { "moduleId": 2, "name": "Result" }, + "typeParams": [ + { + "kind": "primitive", + "primitive": "Int" + }, + { + "kind": "primitive", + "primitive": "String" + } + ] + }, + "body": [ + { + "token": { + "position": [1, 36], + "kind": { + "name": "LParen" + } + }, + "type": { + "kind": "enumInstance", + "enum": { "moduleId": 2, "name": "Result" }, + "typeParams": [ + { + "kind": "primitive", + "primitive": "Int" + }, + { + "kind": "primitive", + "primitive": "String" + } + ] + }, + "node": { + "kind": "invocation", + "invokee": { + "function": "Ok", + "type": { + "kind": "function", + "parameters": [ + { + "required": true, + "type": { + "kind": "generic", + "name": "V" + } + } + ], + "returnType": { + "kind": "enumInstance", + "enum": { "moduleId": 2, "name": "Result" }, + "typeParams": [ + { + "kind": "generic", + "name": "V" + }, + { + "kind": "generic", + "name": "E" + } + ] + } + } + }, + "arguments": [ + { + "token": { + "position": [1, 37], + "kind": { + "name": "Int", + "value": 123 + } + }, + "type": { + "kind": "primitive", + "primitive": "Int" + }, + "node": { + "kind": "literal", + "value": 123 + } + } + ] + } + } + ] + } + } + }, + { + "token": { + "position": [3, 1], + "kind": { + "name": "Func" + } + }, + "type": { + "kind": "primitive", + "primitive": "Unit" + }, + "node": { + "kind": "functionDeclaration", + "function": { + "label": { "name": "f1", "position": [3, 6] }, + "scope": { + "name": "$root::module_3::f1", + "variables": [ + { + "label": { "name": "a", "position": [4, 7] }, + "mutable": true, + "type": { + "kind": "primitive", + "primitive": "Int" + } + } + ], + "functions": [], + "types": [] + }, + "kind": "FunctionKind.Standalone", + "typeParameters": [], + "parameters": [], + "returnType": { + "kind": "enumInstance", + "enum": { "moduleId": 2, "name": "Result" }, + "typeParams": [ + { + "kind": "primitive", + "primitive": "Int" + }, + { + "kind": "primitive", + "primitive": "Int" + } + ] + }, + "body": [ + { + "token": { + "position": [4, 3], + "kind": { + "name": "Var" + } + }, + "type": { + "kind": "primitive", + "primitive": "Unit" + }, + "node": { + "kind": "bindingDeclaration", + "pattern": { + "kind": "variable", + "label": { "name": "a", "position": [4, 7] } + }, + "variables": [ + { + "label": { "name": "a", "position": [4, 7] }, + "mutable": true, + "type": { + "kind": "primitive", + "primitive": "Int" + } + } + ], + "expr": { + "token": { + "position": [4, 11], + "kind": { + "name": "Int", + "value": 0 + } + }, + "type": { + "kind": "primitive", + "primitive": "Int" + }, + "node": { + "kind": "literal", + "value": 0 + } + } + } + }, + { + "token": { + "position": [5, 3], + "kind": { + "name": "While" + } + }, + "type": { + "kind": "primitive", + "primitive": "Unit" + }, + "node": { + "kind": "while", + "condition": { + "token": { + "position": [5, 9], + "kind": { + "name": "Bool", + "value": true + } + }, + "type": { + "kind": "primitive", + "primitive": "Bool" + }, + "node": { + "kind": "literal", + "value": true + } + }, + "conditionBindingPattern": null, + "block": [ + { + "token": { + "position": [6, 5], + "kind": { + "name": "Val" + } + }, + "type": { + "kind": "primitive", + "primitive": "Unit" + }, + "node": { + "kind": "bindingDeclaration", + "pattern": { + "kind": "variable", + "label": { "name": "x", "position": [6, 9] } + }, + "variables": [ + { + "label": { "name": "x", "position": [6, 9] }, + "mutable": false, + "type": { + "kind": "primitive", + "primitive": "Int" + } + } + ], + "expr": { + "token": { + "position": [6, 13], + "kind": { + "name": "Try" + } + }, + "type": { + "kind": "primitive", + "primitive": "Int" + }, + "node": { + "kind": "try", + "expr": { + "token": { + "position": [6, 19], + "kind": { + "name": "LParen" + } + }, + "type": { + "kind": "enumInstance", + "enum": { "moduleId": 2, "name": "Result" }, + "typeParams": [ + { + "kind": "primitive", + "primitive": "Int" + }, + { + "kind": "primitive", + "primitive": "String" + } + ] + }, + "node": { + "kind": "invocation", + "invokee": { + "function": "ok", + "type": { + "kind": "function", + "parameters": [], + "returnType": { + "kind": "enumInstance", + "enum": { "moduleId": 2, "name": "Result" }, + "typeParams": [ + { + "kind": "primitive", + "primitive": "Int" + }, + { + "kind": "primitive", + "primitive": "String" + } + ] + } + } + }, + "arguments": [] + } + }, + "elseBindingPattern": null, + "elseBlock": [ + { + "token": { + "position": [6, 27], + "kind": { + "name": "Break" + } + }, + "type": { + "kind": "never" + }, + "node": { + "kind": "break" + } + } + ] + } + } + } + }, + { + "token": { + "position": [7, 7], + "kind": { + "name": "PlusEq" + } + }, + "type": { + "kind": "primitive", + "primitive": "Unit" + }, + "node": { + "kind": "assignment", + "expr": { + "token": { + "position": [7, 7], + "kind": { + "name": "PlusEq" + } + }, + "type": { + "kind": "primitive", + "primitive": "Int" + }, + "node": { + "kind": "binary", + "op": "BinaryOp.Add", + "left": { + "token": { + "position": [7, 5], + "kind": { + "name": "Ident", + "value": "a" + } + }, + "type": { + "kind": "primitive", + "primitive": "Int" + }, + "node": { + "kind": "identifier", + "name": "a" + } + }, + "right": { + "token": { + "position": [7, 10], + "kind": { + "name": "Ident", + "value": "x" + } + }, + "type": { + "kind": "primitive", + "primitive": "Int" + }, + "node": { + "kind": "identifier", + "name": "x" + } + } + } + }, + "op": "AssignOp.AddEq", + "mode": { + "kind": "variable", + "variable": { + "label": { "name": "a", "position": [4, 7] }, + "mutable": true, + "type": { + "kind": "primitive", + "primitive": "Int" + } + } + } + } + } + ] + } + }, + { + "token": { + "position": [10, 5], + "kind": { + "name": "LParen" + } + }, + "type": { + "kind": "enumInstance", + "enum": { "moduleId": 2, "name": "Result" }, + "typeParams": [ + { + "kind": "primitive", + "primitive": "Int" + }, + { + "kind": "primitive", + "primitive": "Int" + } + ] + }, + "node": { + "kind": "invocation", + "invokee": { + "function": "Ok", + "type": { + "kind": "function", + "parameters": [ + { + "required": true, + "type": { + "kind": "generic", + "name": "V" + } + } + ], + "returnType": { + "kind": "enumInstance", + "enum": { "moduleId": 2, "name": "Result" }, + "typeParams": [ + { + "kind": "generic", + "name": "V" + }, + { + "kind": "generic", + "name": "E" + } + ] + } + } + }, + "arguments": [ + { + "token": { + "position": [10, 6], + "kind": { + "name": "Ident", + "value": "a" + } + }, + "type": { + "kind": "primitive", + "primitive": "Int" + }, + "node": { + "kind": "identifier", + "name": "a" + } + } + ] + } + } + ] + } + } + }, + { + "token": { + "position": [13, 1], + "kind": { + "name": "Func" + } + }, + "type": { + "kind": "primitive", + "primitive": "Unit" + }, + "node": { + "kind": "functionDeclaration", + "function": { + "label": { "name": "f2", "position": [13, 6] }, + "scope": { + "name": "$root::module_3::f2", + "variables": [ + { + "label": { "name": "a", "position": [14, 7] }, + "mutable": true, + "type": { + "kind": "primitive", + "primitive": "Int" + } + } + ], + "functions": [], + "types": [] + }, + "kind": "FunctionKind.Standalone", + "typeParameters": [], + "parameters": [], + "returnType": { + "kind": "enumInstance", + "enum": { "moduleId": 2, "name": "Result" }, + "typeParams": [ + { + "kind": "primitive", + "primitive": "Int" + }, + { + "kind": "primitive", + "primitive": "Int" + } + ] + }, + "body": [ + { + "token": { + "position": [14, 3], + "kind": { + "name": "Var" + } + }, + "type": { + "kind": "primitive", + "primitive": "Unit" + }, + "node": { + "kind": "bindingDeclaration", + "pattern": { + "kind": "variable", + "label": { "name": "a", "position": [14, 7] } + }, + "variables": [ + { + "label": { "name": "a", "position": [14, 7] }, + "mutable": true, + "type": { + "kind": "primitive", + "primitive": "Int" + } + } + ], + "expr": { + "token": { + "position": [14, 11], + "kind": { + "name": "Int", + "value": 0 + } + }, + "type": { + "kind": "primitive", + "primitive": "Int" + }, + "node": { + "kind": "literal", + "value": 0 + } + } + } + }, + { + "token": { + "position": [15, 3], + "kind": { + "name": "While" + } + }, + "type": { + "kind": "primitive", + "primitive": "Unit" + }, + "node": { + "kind": "while", + "condition": { + "token": { + "position": [15, 9], + "kind": { + "name": "Bool", + "value": true + } + }, + "type": { + "kind": "primitive", + "primitive": "Bool" + }, + "node": { + "kind": "literal", + "value": true + } + }, + "conditionBindingPattern": null, + "block": [ + { + "token": { + "position": [16, 5], + "kind": { + "name": "Val" + } + }, + "type": { + "kind": "primitive", + "primitive": "Unit" + }, + "node": { + "kind": "bindingDeclaration", + "pattern": { + "kind": "variable", + "label": { "name": "x", "position": [16, 9] } + }, + "variables": [ + { + "label": { "name": "x", "position": [16, 9] }, + "mutable": false, + "type": { + "kind": "primitive", + "primitive": "Int" + } + } + ], + "expr": { + "token": { + "position": [16, 13], + "kind": { + "name": "Try" + } + }, + "type": { + "kind": "primitive", + "primitive": "Int" + }, + "node": { + "kind": "try", + "expr": { + "token": { + "position": [16, 19], + "kind": { + "name": "LParen" + } + }, + "type": { + "kind": "enumInstance", + "enum": { "moduleId": 2, "name": "Result" }, + "typeParams": [ + { + "kind": "primitive", + "primitive": "Int" + }, + { + "kind": "primitive", + "primitive": "String" + } + ] + }, + "node": { + "kind": "invocation", + "invokee": { + "function": "ok", + "type": { + "kind": "function", + "parameters": [], + "returnType": { + "kind": "enumInstance", + "enum": { "moduleId": 2, "name": "Result" }, + "typeParams": [ + { + "kind": "primitive", + "primitive": "Int" + }, + { + "kind": "primitive", + "primitive": "String" + } + ] + } + } + }, + "arguments": [] + } + }, + "elseBindingPattern": null, + "elseBlock": [ + { + "token": { + "position": [16, 27], + "kind": { + "name": "Continue" + } + }, + "type": { + "kind": "never" + }, + "node": { + "kind": "continue" + } + } + ] + } + } + } + }, + { + "token": { + "position": [17, 7], + "kind": { + "name": "PlusEq" + } + }, + "type": { + "kind": "primitive", + "primitive": "Unit" + }, + "node": { + "kind": "assignment", + "expr": { + "token": { + "position": [17, 7], + "kind": { + "name": "PlusEq" + } + }, + "type": { + "kind": "primitive", + "primitive": "Int" + }, + "node": { + "kind": "binary", + "op": "BinaryOp.Add", + "left": { + "token": { + "position": [17, 5], + "kind": { + "name": "Ident", + "value": "a" + } + }, + "type": { + "kind": "primitive", + "primitive": "Int" + }, + "node": { + "kind": "identifier", + "name": "a" + } + }, + "right": { + "token": { + "position": [17, 10], + "kind": { + "name": "Ident", + "value": "x" + } + }, + "type": { + "kind": "primitive", + "primitive": "Int" + }, + "node": { + "kind": "identifier", + "name": "x" + } + } + } + }, + "op": "AssignOp.AddEq", + "mode": { + "kind": "variable", + "variable": { + "label": { "name": "a", "position": [14, 7] }, + "mutable": true, + "type": { + "kind": "primitive", + "primitive": "Int" + } + } + } + } + } + ] + } + }, + { + "token": { + "position": [20, 5], + "kind": { + "name": "LParen" + } + }, + "type": { + "kind": "enumInstance", + "enum": { "moduleId": 2, "name": "Result" }, + "typeParams": [ + { + "kind": "primitive", + "primitive": "Int" + }, + { + "kind": "primitive", + "primitive": "Int" + } + ] + }, + "node": { + "kind": "invocation", + "invokee": { + "function": "Ok", + "type": { + "kind": "function", + "parameters": [ + { + "required": true, + "type": { + "kind": "generic", + "name": "V" + } + } + ], + "returnType": { + "kind": "enumInstance", + "enum": { "moduleId": 2, "name": "Result" }, + "typeParams": [ + { + "kind": "generic", + "name": "V" + }, + { + "kind": "generic", + "name": "E" + } + ] + } + } + }, + "arguments": [ + { + "token": { + "position": [20, 6], + "kind": { + "name": "Ident", + "value": "a" + } + }, + "type": { + "kind": "primitive", + "primitive": "Int" + }, + "node": { + "kind": "identifier", + "name": "a" + } + } + ] + } + } + ] + } + } + } + ] +} diff --git a/projects/compiler/test/typechecker/try/try.4.abra b/projects/compiler/test/typechecker/try/try.4.abra new file mode 100644 index 00000000..01a05730 --- /dev/null +++ b/projects/compiler/test/typechecker/try/try.4.abra @@ -0,0 +1,13 @@ +func ok(): Result = Ok(123) + +func f1(): Result { + val x = try ok() else return Ok(0) + + Ok(x + 1) +} + +func f2(): Result { + val x = try ok() else return Err(0) + + Ok(x + 1) +} diff --git a/projects/compiler/test/typechecker/try/try.4.out.json b/projects/compiler/test/typechecker/try/try.4.out.json new file mode 100644 index 00000000..bbb21a33 --- /dev/null +++ b/projects/compiler/test/typechecker/try/try.4.out.json @@ -0,0 +1,800 @@ +{ + "id": 3, + "name": "%FILE_NAME%", + "code": [ + { + "token": { + "position": [1, 1], + "kind": { + "name": "Func" + } + }, + "type": { + "kind": "primitive", + "primitive": "Unit" + }, + "node": { + "kind": "functionDeclaration", + "function": { + "label": { "name": "ok", "position": [1, 6] }, + "scope": { + "name": "$root::module_3::ok", + "variables": [], + "functions": [], + "types": [] + }, + "kind": "FunctionKind.Standalone", + "typeParameters": [], + "parameters": [], + "returnType": { + "kind": "enumInstance", + "enum": { "moduleId": 2, "name": "Result" }, + "typeParams": [ + { + "kind": "primitive", + "primitive": "Int" + }, + { + "kind": "primitive", + "primitive": "String" + } + ] + }, + "body": [ + { + "token": { + "position": [1, 36], + "kind": { + "name": "LParen" + } + }, + "type": { + "kind": "enumInstance", + "enum": { "moduleId": 2, "name": "Result" }, + "typeParams": [ + { + "kind": "primitive", + "primitive": "Int" + }, + { + "kind": "primitive", + "primitive": "String" + } + ] + }, + "node": { + "kind": "invocation", + "invokee": { + "function": "Ok", + "type": { + "kind": "function", + "parameters": [ + { + "required": true, + "type": { + "kind": "generic", + "name": "V" + } + } + ], + "returnType": { + "kind": "enumInstance", + "enum": { "moduleId": 2, "name": "Result" }, + "typeParams": [ + { + "kind": "generic", + "name": "V" + }, + { + "kind": "generic", + "name": "E" + } + ] + } + } + }, + "arguments": [ + { + "token": { + "position": [1, 37], + "kind": { + "name": "Int", + "value": 123 + } + }, + "type": { + "kind": "primitive", + "primitive": "Int" + }, + "node": { + "kind": "literal", + "value": 123 + } + } + ] + } + } + ] + } + } + }, + { + "token": { + "position": [3, 1], + "kind": { + "name": "Func" + } + }, + "type": { + "kind": "primitive", + "primitive": "Unit" + }, + "node": { + "kind": "functionDeclaration", + "function": { + "label": { "name": "f1", "position": [3, 6] }, + "scope": { + "name": "$root::module_3::f1", + "variables": [ + { + "label": { "name": "x", "position": [4, 7] }, + "mutable": false, + "type": { + "kind": "primitive", + "primitive": "Int" + } + } + ], + "functions": [], + "types": [] + }, + "kind": "FunctionKind.Standalone", + "typeParameters": [], + "parameters": [], + "returnType": { + "kind": "enumInstance", + "enum": { "moduleId": 2, "name": "Result" }, + "typeParams": [ + { + "kind": "primitive", + "primitive": "Int" + }, + { + "kind": "primitive", + "primitive": "Int" + } + ] + }, + "body": [ + { + "token": { + "position": [4, 3], + "kind": { + "name": "Val" + } + }, + "type": { + "kind": "primitive", + "primitive": "Unit" + }, + "node": { + "kind": "bindingDeclaration", + "pattern": { + "kind": "variable", + "label": { "name": "x", "position": [4, 7] } + }, + "variables": [ + { + "label": { "name": "x", "position": [4, 7] }, + "mutable": false, + "type": { + "kind": "primitive", + "primitive": "Int" + } + } + ], + "expr": { + "token": { + "position": [4, 11], + "kind": { + "name": "Try" + } + }, + "type": { + "kind": "primitive", + "primitive": "Int" + }, + "node": { + "kind": "try", + "expr": { + "token": { + "position": [4, 17], + "kind": { + "name": "LParen" + } + }, + "type": { + "kind": "enumInstance", + "enum": { "moduleId": 2, "name": "Result" }, + "typeParams": [ + { + "kind": "primitive", + "primitive": "Int" + }, + { + "kind": "primitive", + "primitive": "String" + } + ] + }, + "node": { + "kind": "invocation", + "invokee": { + "function": "ok", + "type": { + "kind": "function", + "parameters": [], + "returnType": { + "kind": "enumInstance", + "enum": { "moduleId": 2, "name": "Result" }, + "typeParams": [ + { + "kind": "primitive", + "primitive": "Int" + }, + { + "kind": "primitive", + "primitive": "String" + } + ] + } + } + }, + "arguments": [] + } + }, + "elseBindingPattern": null, + "elseBlock": [ + { + "token": { + "position": [4, 25], + "kind": { + "name": "Return" + } + }, + "type": { + "kind": "never" + }, + "node": { + "kind": "return", + "expr": { + "token": { + "position": [4, 34], + "kind": { + "name": "LParen" + } + }, + "type": { + "kind": "enumInstance", + "enum": { "moduleId": 2, "name": "Result" }, + "typeParams": [ + { + "kind": "primitive", + "primitive": "Int" + }, + { + "kind": "primitive", + "primitive": "Int" + } + ] + }, + "node": { + "kind": "invocation", + "invokee": { + "function": "Ok", + "type": { + "kind": "function", + "parameters": [ + { + "required": true, + "type": { + "kind": "generic", + "name": "V" + } + } + ], + "returnType": { + "kind": "enumInstance", + "enum": { "moduleId": 2, "name": "Result" }, + "typeParams": [ + { + "kind": "generic", + "name": "V" + }, + { + "kind": "generic", + "name": "E" + } + ] + } + } + }, + "arguments": [ + { + "token": { + "position": [4, 35], + "kind": { + "name": "Int", + "value": 0 + } + }, + "type": { + "kind": "primitive", + "primitive": "Int" + }, + "node": { + "kind": "literal", + "value": 0 + } + } + ] + } + } + } + } + ] + } + } + } + }, + { + "token": { + "position": [6, 5], + "kind": { + "name": "LParen" + } + }, + "type": { + "kind": "enumInstance", + "enum": { "moduleId": 2, "name": "Result" }, + "typeParams": [ + { + "kind": "primitive", + "primitive": "Int" + }, + { + "kind": "primitive", + "primitive": "Int" + } + ] + }, + "node": { + "kind": "invocation", + "invokee": { + "function": "Ok", + "type": { + "kind": "function", + "parameters": [ + { + "required": true, + "type": { + "kind": "generic", + "name": "V" + } + } + ], + "returnType": { + "kind": "enumInstance", + "enum": { "moduleId": 2, "name": "Result" }, + "typeParams": [ + { + "kind": "generic", + "name": "V" + }, + { + "kind": "generic", + "name": "E" + } + ] + } + } + }, + "arguments": [ + { + "token": { + "position": [6, 8], + "kind": { + "name": "Plus" + } + }, + "type": { + "kind": "primitive", + "primitive": "Int" + }, + "node": { + "kind": "binary", + "op": "BinaryOp.Add", + "left": { + "token": { + "position": [6, 6], + "kind": { + "name": "Ident", + "value": "x" + } + }, + "type": { + "kind": "primitive", + "primitive": "Int" + }, + "node": { + "kind": "identifier", + "name": "x" + } + }, + "right": { + "token": { + "position": [6, 10], + "kind": { + "name": "Int", + "value": 1 + } + }, + "type": { + "kind": "primitive", + "primitive": "Int" + }, + "node": { + "kind": "literal", + "value": 1 + } + } + } + } + ] + } + } + ] + } + } + }, + { + "token": { + "position": [9, 1], + "kind": { + "name": "Func" + } + }, + "type": { + "kind": "primitive", + "primitive": "Unit" + }, + "node": { + "kind": "functionDeclaration", + "function": { + "label": { "name": "f2", "position": [9, 6] }, + "scope": { + "name": "$root::module_3::f2", + "variables": [ + { + "label": { "name": "x", "position": [10, 7] }, + "mutable": false, + "type": { + "kind": "primitive", + "primitive": "Int" + } + } + ], + "functions": [], + "types": [] + }, + "kind": "FunctionKind.Standalone", + "typeParameters": [], + "parameters": [], + "returnType": { + "kind": "enumInstance", + "enum": { "moduleId": 2, "name": "Result" }, + "typeParams": [ + { + "kind": "primitive", + "primitive": "Int" + }, + { + "kind": "primitive", + "primitive": "Int" + } + ] + }, + "body": [ + { + "token": { + "position": [10, 3], + "kind": { + "name": "Val" + } + }, + "type": { + "kind": "primitive", + "primitive": "Unit" + }, + "node": { + "kind": "bindingDeclaration", + "pattern": { + "kind": "variable", + "label": { "name": "x", "position": [10, 7] } + }, + "variables": [ + { + "label": { "name": "x", "position": [10, 7] }, + "mutable": false, + "type": { + "kind": "primitive", + "primitive": "Int" + } + } + ], + "expr": { + "token": { + "position": [10, 11], + "kind": { + "name": "Try" + } + }, + "type": { + "kind": "primitive", + "primitive": "Int" + }, + "node": { + "kind": "try", + "expr": { + "token": { + "position": [10, 17], + "kind": { + "name": "LParen" + } + }, + "type": { + "kind": "enumInstance", + "enum": { "moduleId": 2, "name": "Result" }, + "typeParams": [ + { + "kind": "primitive", + "primitive": "Int" + }, + { + "kind": "primitive", + "primitive": "String" + } + ] + }, + "node": { + "kind": "invocation", + "invokee": { + "function": "ok", + "type": { + "kind": "function", + "parameters": [], + "returnType": { + "kind": "enumInstance", + "enum": { "moduleId": 2, "name": "Result" }, + "typeParams": [ + { + "kind": "primitive", + "primitive": "Int" + }, + { + "kind": "primitive", + "primitive": "String" + } + ] + } + } + }, + "arguments": [] + } + }, + "elseBindingPattern": null, + "elseBlock": [ + { + "token": { + "position": [10, 25], + "kind": { + "name": "Return" + } + }, + "type": { + "kind": "never" + }, + "node": { + "kind": "return", + "expr": { + "token": { + "position": [10, 35], + "kind": { + "name": "LParen" + } + }, + "type": { + "kind": "enumInstance", + "enum": { "moduleId": 2, "name": "Result" }, + "typeParams": [ + { + "kind": "primitive", + "primitive": "Int" + }, + { + "kind": "primitive", + "primitive": "Int" + } + ] + }, + "node": { + "kind": "invocation", + "invokee": { + "function": "Err", + "type": { + "kind": "function", + "parameters": [ + { + "required": true, + "type": { + "kind": "generic", + "name": "E" + } + } + ], + "returnType": { + "kind": "enumInstance", + "enum": { "moduleId": 2, "name": "Result" }, + "typeParams": [ + { + "kind": "generic", + "name": "V" + }, + { + "kind": "generic", + "name": "E" + } + ] + } + } + }, + "arguments": [ + { + "token": { + "position": [10, 36], + "kind": { + "name": "Int", + "value": 0 + } + }, + "type": { + "kind": "primitive", + "primitive": "Int" + }, + "node": { + "kind": "literal", + "value": 0 + } + } + ] + } + } + } + } + ] + } + } + } + }, + { + "token": { + "position": [12, 5], + "kind": { + "name": "LParen" + } + }, + "type": { + "kind": "enumInstance", + "enum": { "moduleId": 2, "name": "Result" }, + "typeParams": [ + { + "kind": "primitive", + "primitive": "Int" + }, + { + "kind": "primitive", + "primitive": "Int" + } + ] + }, + "node": { + "kind": "invocation", + "invokee": { + "function": "Ok", + "type": { + "kind": "function", + "parameters": [ + { + "required": true, + "type": { + "kind": "generic", + "name": "V" + } + } + ], + "returnType": { + "kind": "enumInstance", + "enum": { "moduleId": 2, "name": "Result" }, + "typeParams": [ + { + "kind": "generic", + "name": "V" + }, + { + "kind": "generic", + "name": "E" + } + ] + } + } + }, + "arguments": [ + { + "token": { + "position": [12, 8], + "kind": { + "name": "Plus" + } + }, + "type": { + "kind": "primitive", + "primitive": "Int" + }, + "node": { + "kind": "binary", + "op": "BinaryOp.Add", + "left": { + "token": { + "position": [12, 6], + "kind": { + "name": "Ident", + "value": "x" + } + }, + "type": { + "kind": "primitive", + "primitive": "Int" + }, + "node": { + "kind": "identifier", + "name": "x" + } + }, + "right": { + "token": { + "position": [12, 10], + "kind": { + "name": "Int", + "value": 1 + } + }, + "type": { + "kind": "primitive", + "primitive": "Int" + }, + "node": { + "kind": "literal", + "value": 1 + } + } + } + } + ] + } + } + ] + } + } + } + ] +}