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()); + } + +}