diff --git a/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java b/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java index 9dc5fe6..49deeb3 100644 --- a/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java +++ b/src/main/java/de/hsrm/compiler/Klang/ContextAnalysis.java @@ -179,43 +179,54 @@ public class ContextAnalysis extends KlangBaseVisitor { @Override public Node visitVariable_declaration(KlangParser.Variable_declarationContext ctx) { - String name = ctx.IDENT().getText(); - int line = ctx.start.getLine(); - int col = ctx.start.getCharPositionInLine(); - Type declaredType = Type.getByName(ctx.type_annotation().type().getText()); + var variableName = ctx.IDENT().getText(); + var declaredType = Type.getByName(ctx.type_annotation().type().getText()); + var line = ctx.start.getLine(); + var col = ctx.start.getCharPositionInLine(); - if (!declaredType.isPrimitiveType() && this.structDefs.get(declaredType.getName()) == null) { - String error = "Type " + declaredType.getName() + " not defined."; + 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); } - if (this.vars.get(name) != null) { - String error = "Redeclaration of variable with name \"" + name + "\"."; + if (structDefs.containsKey(variableName)) { + var error = "Variable name " + variableName + " shadows a struct of the same name."; + throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); + } + + if (enumDefs.containsKey(variableName)) { + var error = "Variable name " + variableName + " shadows an enum of the same name."; + throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); + } + + if (vars.get(variableName) != null) { + var error = "Redeclaration of variable with name \"" + variableName + "\"."; throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); } // Create the appropriate instance - VariableDeclaration result; + VariableDeclaration variableDeclaration; if (ctx.expression() != null) { - Node expression = this.visit(ctx.expression()); + var expression = visit(ctx.expression()); try { declaredType.combine(expression.type); } catch (Exception e) { throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage()); } - result = new VariableDeclaration(name, (Expression) expression); - result.initialized = true; + variableDeclaration = new VariableDeclaration(variableName, (Expression) expression); + variableDeclaration.initialized = true; } else { - result = new VariableDeclaration(name); + variableDeclaration = new VariableDeclaration(variableName); } // Add it to the global map of variable declarations - this.vars.put(name, result); + vars.put(variableName, variableDeclaration); - result.line = line; - result.col = col; - result.type = declaredType; - return result; + variableDeclaration.line = line; + variableDeclaration.col = col; + variableDeclaration.type = declaredType; + + return variableDeclaration; } @Override diff --git a/src/test/java/VariableDeclarationTest.java b/src/test/java/VariableDeclarationTest.java index acc1d1c..5dba488 100644 --- a/src/test/java/VariableDeclarationTest.java +++ b/src/test/java/VariableDeclarationTest.java @@ -1,34 +1,94 @@ -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import org.antlr.v4.runtime.tree.ParseTree; +import de.hsrm.compiler.Klang.ContextAnalysis; import org.junit.jupiter.api.Test; -import de.hsrm.compiler.Klang.ContextAnalysis; +import static org.junit.jupiter.api.Assertions.*; public class VariableDeclarationTest { @Test - void typeNotDefined() { - ParseTree tree = Helper.prepareParser("function foo(): int { let X: unk; return 1; } foo();"); - var funcs = Helper.getFuncs(tree); - var structs = Helper.getStructs(tree); - var enums = Helper.getEnums(tree); - ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums); - - Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); + void shouldNotThrowIfDeclaredTypeIsAStruct() { + // given + var tree = Helper.prepareParser("struct bar { a: int; } function foo(): int { let a: bar; return 1; } foo();"); + var ctxAnal = new ContextAnalysis( + Helper.getFuncs(tree), + Helper.getStructs(tree), + Helper.getEnums(tree) + ); + + // when / then + assertDoesNotThrow(() -> ctxAnal.visit(tree)); + } + + @Test + void shouldNotThrowIfDeclaredTypeIsAnEnum() { + // given + var tree = Helper.prepareParser("enum bar { A, B } function foo(): int { let a: bar; return 1; } foo();"); + var ctxAnal = new ContextAnalysis( + Helper.getFuncs(tree), + Helper.getStructs(tree), + Helper.getEnums(tree) + ); + + // when / then + assertDoesNotThrow(() -> ctxAnal.visit(tree)); + } + + @Test + void shouldThrowExceptionIfDeclaredNameShadowsEnumName() { + // given + var tree = Helper.prepareParser("enum bar { A, B } function foo(): int { let bar: 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:40 Variable name bar shadows an enum of the same name.", e.getMessage()); + } + + @Test + void shouldThrowExceptionIfDeclaredNameShadowsStruct() { + // given + var tree = Helper.prepareParser("struct bar { a: int; } function foo(): int { let bar: 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:45 Variable name bar shadows a struct of the same name.", e.getMessage()); + } + + @Test + void shouldThrowExceptionIfDeclaredTypeIsNotDefined() { + // given + var tree = Helper.prepareParser("function foo(): int { let X: unk; 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 unk not defined.", e.getMessage()); } @Test - void variableRedeclaration() - { - ParseTree tree = Helper.prepareParser("function foo(): int { let x: int; let x: bool; return 1; } foo();"); - var funcs = Helper.getFuncs(tree); - var structs = Helper.getStructs(tree); - var enums = Helper.getEnums(tree); - ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums); - - Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); + void shouldThrowExceptionIfVariableIsRedeclared() { + // given + var tree = Helper.prepareParser("function foo(): int { let x: int; let x: bool; 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:34 Redeclaration of variable with name \"x\".", e.getMessage()); } } \ No newline at end of file