From 53976615e131f79f194a255548197338f2770158 Mon Sep 17 00:00:00 2001 From: Marvin Kaiser Date: Mon, 9 Mar 2020 21:08:04 +0100 Subject: [PATCH 1/3] 31: Add void type --- .../antlr4/de/hsrm/compiler/Klang/Klang.g4 | 6 ++- .../hsrm/compiler/Klang/ContextAnalysis.java | 38 ++++++++++++---- .../java/de/hsrm/compiler/Klang/Klang.java | 8 +++- .../nodes/statements/ReturnStatement.java | 4 ++ .../de/hsrm/compiler/Klang/types/Type.java | 7 ++- .../hsrm/compiler/Klang/types/VoidType.java | 45 +++++++++++++++++++ .../compiler/Klang/visitors/EvalVisitor.java | 3 ++ .../hsrm/compiler/Klang/visitors/GenASM.java | 6 ++- .../hsrm/compiler/Klang/visitors/GetVars.java | 4 +- .../Klang/visitors/PrettyPrintVisitor.java | 7 ++- 10 files changed, 110 insertions(+), 18 deletions(-) create mode 100644 src/main/java/de/hsrm/compiler/Klang/types/VoidType.java diff --git a/src/main/antlr4/de/hsrm/compiler/Klang/Klang.g4 b/src/main/antlr4/de/hsrm/compiler/Klang/Klang.g4 index efe054d..f752ddb 100644 --- a/src/main/antlr4/de/hsrm/compiler/Klang/Klang.g4 +++ b/src/main/antlr4/de/hsrm/compiler/Klang/Klang.g4 @@ -33,7 +33,7 @@ parameter ; braced_block - : OBRK statement+ CBRK + : OBRK (statement | functionCall SCOL)+ CBRK ; @@ -73,7 +73,7 @@ field_assignment ; return_statement - : RETURN expression SCOL + : RETURN expression? SCOL ; destroy_statement @@ -120,6 +120,7 @@ type | BOOLEAN | FLOAT | IDENT + | VOID ; functionCall @@ -186,6 +187,7 @@ DIV: '/'; BOOLEAN: 'bool'; INTEGER: 'int'; FLOAT: 'float'; +VOID: 'void'; INTEGER_LITERAL : [0-9]+ diff --git a/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java b/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java index a2323a9..982237e 100644 --- a/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java +++ b/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java @@ -186,6 +186,11 @@ public class ContextAnalysis extends KlangBaseVisitor { var line = ctx.start.getLine(); var col = ctx.start.getCharPositionInLine(); + if (declaredType.equals(Type.getVoidType())) { + var error = "Type " + declaredType.getName() + " can not be used to declare variables."; + throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); + } + if (!declaredType.isPrimitiveType() && !structDefs.containsKey(declaredType.getName()) && !enumDefs.containsKey(declaredType.getName())) { var error = "Type " + declaredType.getName() + " not defined."; throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); @@ -266,6 +271,18 @@ public class ContextAnalysis extends KlangBaseVisitor { @Override public Node visitReturn_statement(KlangParser.Return_statementContext ctx) { + if (currentDeclaredReturnType.equals(Type.getVoidType())) { + ReturnStatement result = new ReturnStatement(); + result.type = Type.getVoidType(); + result.line = ctx.start.getLine(); + result.col = ctx.start.getCharPositionInLine(); + if (ctx.expression() != null) { + String error = "Cannot return an expression from a void function."; + throw new RuntimeException(Helper.getErrorPrefix(result.line, result.col) + error); + } + return result; + } + Expression expression = (Expression) this.visit(ctx.expression()); ReturnStatement result = new ReturnStatement(expression); @@ -277,8 +294,8 @@ public class ContextAnalysis extends KlangBaseVisitor { } } - result.type = expression.type; result.line = ctx.start.getLine(); + result.type = expression.type; result.col = ctx.start.getCharPositionInLine(); return result; } @@ -826,26 +843,29 @@ public class ContextAnalysis extends KlangBaseVisitor { public Node visitParameter(KlangParser.ParameterContext ctx) { var parameterName = ctx.IDENT().getText(); var parameterType = Type.getByName(ctx.type_annotation().type().getText()); + var line = ctx.start.getLine(); + var col = ctx.start.getCharPositionInLine(); + + if (parameterType.equals(Type.getVoidType())) { + var error = "Type " + parameterType.getName() + " cannot be used to declare a parameter."; + throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); + } if (structDefs.containsKey(parameterName)) { - var line = ctx.start.getLine(); - var col = ctx.start.getCharPositionInLine(); var error = "Parameter name " + parameterName + " duplicates a struct of the same name."; throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); } if (enumDefs.containsKey(parameterName)) { - var line = ctx.start.getLine(); - var col = ctx.start.getCharPositionInLine(); var error = "Parameter name " + parameterName + " duplicates an enum of the same name."; throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); } if (!parameterType.isPrimitiveType() && !structDefs.containsKey(parameterType.getName()) && !enumDefs.containsKey(parameterType.getName())) { - var line = ctx.type_annotation().start.getLine(); - var col = ctx.type_annotation().start.getCharPositionInLine(); + var typeLine = ctx.type_annotation().start.getLine(); + var typeCol = ctx.type_annotation().start.getCharPositionInLine(); var error = "Type " + parameterType.getName() + " not defined."; - throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); + throw new RuntimeException(Helper.getErrorPrefix(typeLine, typeCol) + error); } var parameter = new Parameter(parameterName); @@ -983,4 +1003,4 @@ public class ContextAnalysis extends KlangBaseVisitor { } return path; } -} \ No newline at end of file +} diff --git a/src/main/java/de/hsrm/compiler/Klang/Klang.java b/src/main/java/de/hsrm/compiler/Klang/Klang.java index ca8094c..efa84fe 100644 --- a/src/main/java/de/hsrm/compiler/Klang/Klang.java +++ b/src/main/java/de/hsrm/compiler/Klang/Klang.java @@ -1,9 +1,11 @@ package de.hsrm.compiler.Klang; +import de.hsrm.compiler.Klang.helper.*; import de.hsrm.compiler.Klang.nodes.EnumDefinition; import de.hsrm.compiler.Klang.nodes.FunctionDefinition; import de.hsrm.compiler.Klang.nodes.Node; import de.hsrm.compiler.Klang.nodes.StructDefinition; +import de.hsrm.compiler.Klang.types.Type; import de.hsrm.compiler.Klang.visitors.EvalVisitor; import de.hsrm.compiler.Klang.visitors.GenASM; import de.hsrm.compiler.Klang.visitors.PrettyPrintVisitor; @@ -120,7 +122,11 @@ public class Klang { System.out.println("\nEvaluating the source code:"); EvalVisitor evalVisitor = new EvalVisitor(structDefs); Value result = root.welcome(evalVisitor); - generateOutput(out, "Result was: " + result.asObject().toString()); + if (result.type.equals(Type.getVoidType())) { + generateOutput(out, "Result was void"); + } else { + generateOutput(out, "Result was: " + result.asObject().toString()); + } return; } diff --git a/src/main/java/de/hsrm/compiler/Klang/nodes/statements/ReturnStatement.java b/src/main/java/de/hsrm/compiler/Klang/nodes/statements/ReturnStatement.java index ef37a52..e98ddf7 100644 --- a/src/main/java/de/hsrm/compiler/Klang/nodes/statements/ReturnStatement.java +++ b/src/main/java/de/hsrm/compiler/Klang/nodes/statements/ReturnStatement.java @@ -11,6 +11,10 @@ public class ReturnStatement extends Statement { this.expression = expression; } + public ReturnStatement() { + this.expression = null; + } + @Override public R welcome(Visitor v) { return v.visit(this); diff --git a/src/main/java/de/hsrm/compiler/Klang/types/Type.java b/src/main/java/de/hsrm/compiler/Klang/types/Type.java index 4ce91ca..a8261df 100644 --- a/src/main/java/de/hsrm/compiler/Klang/types/Type.java +++ b/src/main/java/de/hsrm/compiler/Klang/types/Type.java @@ -27,12 +27,17 @@ public abstract class Type { return NullType.getType(); } + public static VoidType getVoidType() { + return VoidType.getType(); + } + public static Type getByName(String name) { switch (name) { case "bool": return getBooleanType(); case "int": return getIntegerType(); case "float": return getFloatType(); case "null": return getNullType(); + case "void": return getVoidType(); default: return new NamedType(name); } } @@ -42,4 +47,4 @@ public abstract class Type { public abstract boolean valuesEqual(Value a, Value b); public abstract boolean isPrimitiveType(); public abstract boolean isNumericType(); -} \ No newline at end of file +} diff --git a/src/main/java/de/hsrm/compiler/Klang/types/VoidType.java b/src/main/java/de/hsrm/compiler/Klang/types/VoidType.java new file mode 100644 index 0000000..77730c9 --- /dev/null +++ b/src/main/java/de/hsrm/compiler/Klang/types/VoidType.java @@ -0,0 +1,45 @@ +package de.hsrm.compiler.Klang.types; + +import de.hsrm.compiler.Klang.Value; + +public class VoidType extends Type { + + private static VoidType instance; + + public static VoidType getType() { + if (instance != null) { + return instance; + } + instance = new VoidType(); + return instance; +} + + @Override + public String getName() { + return "void"; + } + + @Override + public Type combine(Type that) { + if (that.equals(this)) { + return this; + } + throw new RuntimeException("Type mismatch: cannot combine " + this.getName() + " and " + that.getName()); + } + + @Override + public boolean valuesEqual(Value a, Value b) { + throw new RuntimeException("Can not compare void types."); + } + + @Override + public boolean isPrimitiveType() { + return false; + } + + @Override + public boolean isNumericType() { + return false; + } + +} \ No newline at end of file diff --git a/src/main/java/de/hsrm/compiler/Klang/visitors/EvalVisitor.java b/src/main/java/de/hsrm/compiler/Klang/visitors/EvalVisitor.java index c1b93ee..ee6193a 100644 --- a/src/main/java/de/hsrm/compiler/Klang/visitors/EvalVisitor.java +++ b/src/main/java/de/hsrm/compiler/Klang/visitors/EvalVisitor.java @@ -396,6 +396,9 @@ public class EvalVisitor implements Visitor { @Override public Value visit(ReturnStatement e) { + if (e.expression == null) { + return new Value(null, Type.getVoidType()); + } return e.expression.welcome(this); } diff --git a/src/main/java/de/hsrm/compiler/Klang/visitors/GenASM.java b/src/main/java/de/hsrm/compiler/Klang/visitors/GenASM.java index c02db48..4618c3d 100644 --- a/src/main/java/de/hsrm/compiler/Klang/visitors/GenASM.java +++ b/src/main/java/de/hsrm/compiler/Klang/visitors/GenASM.java @@ -525,7 +525,9 @@ public class GenASM implements Visitor { @Override public Void visit(ReturnStatement e) { - e.expression.welcome(this); + if (e.expression != null) { + e.expression.welcome(this); + } // The ReturnStatement visitor is kindly removing the // stack space that was allocated by the FunctionDefinition @@ -1020,4 +1022,4 @@ public class GenASM implements Visitor { return false; } } -} \ No newline at end of file +} diff --git a/src/main/java/de/hsrm/compiler/Klang/visitors/GetVars.java b/src/main/java/de/hsrm/compiler/Klang/visitors/GetVars.java index 33e6b6b..45220bc 100644 --- a/src/main/java/de/hsrm/compiler/Klang/visitors/GetVars.java +++ b/src/main/java/de/hsrm/compiler/Klang/visitors/GetVars.java @@ -194,7 +194,9 @@ class GetVars implements Visitor { @Override public Void visit(ReturnStatement e) { - e.expression.welcome(this); + if (e.expression != null) { + e.expression.welcome(this); + } return null; } diff --git a/src/main/java/de/hsrm/compiler/Klang/visitors/PrettyPrintVisitor.java b/src/main/java/de/hsrm/compiler/Klang/visitors/PrettyPrintVisitor.java index 252797b..470e3dd 100644 --- a/src/main/java/de/hsrm/compiler/Klang/visitors/PrettyPrintVisitor.java +++ b/src/main/java/de/hsrm/compiler/Klang/visitors/PrettyPrintVisitor.java @@ -310,8 +310,11 @@ public class PrettyPrintVisitor implements Visitor { @Override public Void visit(ReturnStatement e) { - ex.write("return "); - e.expression.welcome(this); + ex.write("return"); + if (e.expression != null) { + ex.write(" "); + e.expression.welcome(this); + } ex.write(";"); return null; } From 26eff47057388802ac5718b55a45ec6cb1eaa858 Mon Sep 17 00:00:00 2001 From: nitrix Date: Thu, 23 Mar 2023 13:09:52 +0100 Subject: [PATCH 2/3] Fix typos. --- src/main/java/de/hsrm/compiler/Klang/types/BooleanType.java | 2 +- src/main/java/de/hsrm/compiler/Klang/types/FloatType.java | 2 +- src/main/java/de/hsrm/compiler/Klang/types/IntegerType.java | 2 +- src/main/java/de/hsrm/compiler/Klang/types/NullType.java | 2 +- src/test/java/ConstructorCallTest.java | 4 ++-- src/test/java/FunctionCallTest.java | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/de/hsrm/compiler/Klang/types/BooleanType.java b/src/main/java/de/hsrm/compiler/Klang/types/BooleanType.java index 8e3f27a..0964131 100644 --- a/src/main/java/de/hsrm/compiler/Klang/types/BooleanType.java +++ b/src/main/java/de/hsrm/compiler/Klang/types/BooleanType.java @@ -32,7 +32,7 @@ public class BooleanType extends PrimitiveType { } // Every remaining type will throw a RuntimeException - throw new RuntimeException("Type missmatch: cannot combine " + this.getName() + " and " + that.getName()); + throw new RuntimeException("Type mismatch: cannot combine " + this.getName() + " and " + that.getName()); } @Override diff --git a/src/main/java/de/hsrm/compiler/Klang/types/FloatType.java b/src/main/java/de/hsrm/compiler/Klang/types/FloatType.java index cb383d5..74b68a8 100644 --- a/src/main/java/de/hsrm/compiler/Klang/types/FloatType.java +++ b/src/main/java/de/hsrm/compiler/Klang/types/FloatType.java @@ -36,7 +36,7 @@ public class FloatType extends NumericType { } // Every remaining type will throw a RuntimeException - throw new RuntimeException("Type missmatch: cannot combine " + this.getName() + " and " + that.getName()); + throw new RuntimeException("Type mismatch: cannot combine " + this.getName() + " and " + that.getName()); } @Override diff --git a/src/main/java/de/hsrm/compiler/Klang/types/IntegerType.java b/src/main/java/de/hsrm/compiler/Klang/types/IntegerType.java index 7eccf14..f413d5b 100644 --- a/src/main/java/de/hsrm/compiler/Klang/types/IntegerType.java +++ b/src/main/java/de/hsrm/compiler/Klang/types/IntegerType.java @@ -36,7 +36,7 @@ public class IntegerType extends NumericType { } // Every remaining type will throw a RuntimeException - throw new RuntimeException("Type missmatch: cannot combine " + this.getName() + " and " + that.getName()); + throw new RuntimeException("Type mismatch: cannot combine " + this.getName() + " and " + that.getName()); } @Override diff --git a/src/main/java/de/hsrm/compiler/Klang/types/NullType.java b/src/main/java/de/hsrm/compiler/Klang/types/NullType.java index 0e2cf62..dbb0505 100644 --- a/src/main/java/de/hsrm/compiler/Klang/types/NullType.java +++ b/src/main/java/de/hsrm/compiler/Klang/types/NullType.java @@ -23,7 +23,7 @@ public class NullType extends Type { public Type combine(Type that) { // You can not combine null with a primitive type if (that.isPrimitiveType()) { - throw new RuntimeException("Type missmatch: cannot combine " + this.getName() + " and " + that.getName()); + throw new RuntimeException("Type mismatch: cannot combine " + this.getName() + " and " + that.getName()); } // Everything else combines with null to the type it was before diff --git a/src/test/java/ConstructorCallTest.java b/src/test/java/ConstructorCallTest.java index 9d21d29..343bfa0 100644 --- a/src/test/java/ConstructorCallTest.java +++ b/src/test/java/ConstructorCallTest.java @@ -21,7 +21,7 @@ public class ConstructorCallTest { } @Test - void numConstructorParameterMissmatch() { + void numConstructorParametermismatch() { ParseTree tree = Helper.prepareParser("struct bar { a: int; } function foo(): bar { return create bar(1, false); } foo();"); var funcs = Helper.getFuncs(tree); var structs = Helper.getStructs(tree); @@ -41,6 +41,6 @@ public class ConstructorCallTest { ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums); Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); - assertEquals("Error in line 1:63 argument 0 Type missmatch: cannot combine bool and int", e.getMessage()); + assertEquals("Error in line 1:63 argument 0 Type mismatch: cannot combine bool and int", e.getMessage()); } } \ No newline at end of file diff --git a/src/test/java/FunctionCallTest.java b/src/test/java/FunctionCallTest.java index ce3876f..0a1dea8 100644 --- a/src/test/java/FunctionCallTest.java +++ b/src/test/java/FunctionCallTest.java @@ -33,7 +33,7 @@ public class FunctionCallTest { } @Test - void parameterTypeMissmatch() { + void parameterTypeMismatch() { ParseTree tree = Helper.prepareParser("function foo(x: int): int { return x; } foo(false);"); var funcs = Helper.getFuncs(tree); var structs = Helper.getStructs(tree); From 7965c89a604c7561e6f7b5a112fab72def28ad29 Mon Sep 17 00:00:00 2001 From: nitrix Date: Thu, 23 Mar 2023 13:10:20 +0100 Subject: [PATCH 3/3] VoidType: Add tests and fix some bugs --- .../hsrm/compiler/Klang/ContextAnalysis.java | 37 ++++--- src/test/java/VoidTest.java | 101 ++++++++++++++++++ 2 files changed, 123 insertions(+), 15 deletions(-) create mode 100644 src/test/java/VoidTest.java diff --git a/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java b/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java index 982237e..0b2a74a 100644 --- a/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java +++ b/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java @@ -68,13 +68,13 @@ public class ContextAnalysis extends KlangBaseVisitor { @Override public Node visitBraced_block(KlangParser.Braced_blockContext ctx) { - int actualStatementCount = 0; - int declaredStatementCount = ctx.statement().size(); - boolean hasReturn = false; - Statement[] statements = new Statement[declaredStatementCount]; + var actualStatementCount = 0; + var declaredStatementCount = ctx.statement().size(); + var hasReturn = false; + var statements = new Statement[declaredStatementCount]; for (int i = 0; i < declaredStatementCount; i++) { - Node currentStatement = this.visit(ctx.statement(i)); + var currentStatement = visit(ctx.statement(i)); statements[i] = (Statement) currentStatement; actualStatementCount += 1; @@ -83,7 +83,7 @@ public class ContextAnalysis extends KlangBaseVisitor { if (currentStatement.type != null && !(currentStatement instanceof VariableDeclaration)) { // check whether the type matches try { - this.currentDeclaredReturnType.combine(currentStatement.type); + currentDeclaredReturnType.combine(currentStatement.type); } catch (Exception e) { throw new RuntimeException( Helper.getErrorPrefix(currentStatement.line, currentStatement.col) + e.getMessage()); @@ -99,14 +99,15 @@ public class ContextAnalysis extends KlangBaseVisitor { // If there was unreachable code in this block, // create a shorter statements array and copy the statements to there if (actualStatementCount < declaredStatementCount) { - Statement[] newStatements = new Statement[actualStatementCount]; + var newStatements = new Statement[actualStatementCount]; System.arraycopy(statements, 0, newStatements, 0, actualStatementCount); statements = newStatements; } // if this block contains at least one statement that guarantees a return value, // we indicate that this block guarantees a return value by setting result.type - Block result = new Block(statements); + var result = new Block(statements); + if (hasReturn) { result.type = this.currentDeclaredReturnType; } @@ -272,28 +273,28 @@ public class ContextAnalysis extends KlangBaseVisitor { @Override public Node visitReturn_statement(KlangParser.Return_statementContext ctx) { if (currentDeclaredReturnType.equals(Type.getVoidType())) { - ReturnStatement result = new ReturnStatement(); + var result = new ReturnStatement(); result.type = Type.getVoidType(); result.line = ctx.start.getLine(); result.col = ctx.start.getCharPositionInLine(); if (ctx.expression() != null) { - String error = "Cannot return an expression from a void function."; + var error = "Cannot return an expression from a void function."; throw new RuntimeException(Helper.getErrorPrefix(result.line, result.col) + error); } return result; } - Expression expression = (Expression) this.visit(ctx.expression()); - ReturnStatement result = new ReturnStatement(expression); + var expression = (Expression) visit(ctx.expression()); // Check if this expression is a tail recursion if (expression instanceof FunctionCall funCall) { - if (funCall.name.equals(this.currentFunctionDefinitionName)) { + if (funCall.name.equals(currentFunctionDefinitionName)) { // Flag this function call funCall.isTailRecursive = true; } } + var result = new ReturnStatement(expression); result.line = ctx.start.getLine(); result.type = expression.type; result.col = ctx.start.getCharPositionInLine(); @@ -766,6 +767,11 @@ public class ContextAnalysis extends KlangBaseVisitor { var line = ctx.start.getLine(); var col = ctx.start.getCharPositionInLine(); + if (structFieldType.equals(Type.getVoidType())) { + var error = "Type void can not be used as a struct field type."; + throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); + } + if (!structFieldType.isPrimitiveType() && !structDefs.containsKey(structFieldType.getName()) && !enumDefs.containsKey(structFieldType.getName())) { var error = "Type " + structFieldType.getName() + " not defined."; throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); @@ -796,7 +802,8 @@ public class ContextAnalysis extends KlangBaseVisitor { currentDeclaredReturnType = returnType; currentFunctionDefinitionName = name; - if (!returnType.isPrimitiveType() && !structDefs.containsKey(returnType.getName()) && !enumDefs.containsKey(returnType.getName())) { + var typeNotDefined = !returnType.isPrimitiveType() && !structDefs.containsKey(returnType.getName()) && !enumDefs.containsKey(returnType.getName()); + if (!returnType.equals(Type.getVoidType()) && typeNotDefined) { var line = ctx.returnType.start.getLine(); var col = ctx.returnType.start.getCharPositionInLine(); var error = "Type " + returnType.getName() + " not defined."; @@ -824,7 +831,7 @@ public class ContextAnalysis extends KlangBaseVisitor { // Visit the block, make sure that a return value is guaranteed var block = visit(ctx.braced_block()); - if (block.type == null) { + if (block.type == null && !returnType.equals(Type.getVoidType())) { var line = ctx.braced_block().start.getLine(); var col = ctx.braced_block().start.getCharPositionInLine(); var error = "Function " + name + " has to return something of type " + returnType.getName() + "."; diff --git a/src/test/java/VoidTest.java b/src/test/java/VoidTest.java new file mode 100644 index 0000000..30418f3 --- /dev/null +++ b/src/test/java/VoidTest.java @@ -0,0 +1,101 @@ +import de.hsrm.compiler.Klang.ContextAnalysis; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class VoidTest { + + @Test + void shouldNotThrowIfVoidIsUsedAsReturnType() { + // given + var tree = Helper.prepareParser("function foo(): void { let a: int = 0; } foo();"); + var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree)); + + // when / then + assertDoesNotThrow(() -> ctxAnal.visit(tree)); + } + + @Test + void shouldNotThrowIfVoidFunctionIsCalled() { + // given + var tree = Helper.prepareParser(""" + function foo(): void { + let a: int = 0; + } + function bar(): int { + foo(); + return 1; + } + bar(); + """); + var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree)); + + // when / then + assertDoesNotThrow(() -> ctxAnal.visit(tree)); + } + + @Test + void shouldThrowIfVoidFunctionIsAssignedToVariable() { + // given + var tree = Helper.prepareParser(""" + function foo(): void { + let a: int = 0; + } + function bar(): int { + let a: int = foo(); + return 1; + } + bar(); + """); + var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree)); + + // when / then + var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); + assertEquals("Error in line 5:4 Type mismatch: cannot combine int and void", e.getMessage()); + } + + @Test + void shouldThrowIfValueIsReturnedFromVoidFunction() { + // given + var tree = Helper.prepareParser("function foo(): void { return 1; } foo();"); + var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree)); + + // when / then + var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); + assertEquals("Error in line 1:23 Cannot return an expression from a void function.", e.getMessage()); + } + + @Test + void shouldThrowIfVoidIsUsedAsParameterType() { + // given + var tree = Helper.prepareParser("function foo(a: void): int { return 1; } foo();"); + var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree)); + + // when / then + var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); + assertEquals("Error in line 1:13 Type void cannot be used to declare a parameter.", e.getMessage()); + } + + @Test + void shouldThrowIfVoidIsUsedAsVariableType() { + // given + var tree = Helper.prepareParser("function foo(): int { let a: void; return 1; } foo();"); + var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree)); + + // when / then + var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); + assertEquals("Error in line 1:22 Type void can not be used to declare variables.", e.getMessage()); + } + + @Test + void shouldThrowIfVoidIsUsedAsStructFieldType() { + // given + var tree = Helper.prepareParser("struct bar { a: void; } function foo(): int { return 1; } foo();"); + var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree)); + + // when / then + var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); + assertEquals("Error in line 1:13 Type void can not be used as a struct field type.", e.getMessage()); + } + +}