VoidType: Add tests and fix some bugs
This commit is contained in:
@@ -68,13 +68,13 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
|
||||
|
||||
@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<Node> {
|
||||
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<Node> {
|
||||
// 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<Node> {
|
||||
@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<Node> {
|
||||
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<Node> {
|
||||
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<Node> {
|
||||
|
||||
// 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() + ".";
|
||||
|
||||
101
src/test/java/VoidTest.java
Normal file
101
src/test/java/VoidTest.java
Normal file
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user