ContextAnalysis: Make comparing more generous
Previously we only allowed numeric types to be compared using >,<,<= and >=. Now we allow all primitives types to be compared using the operators mentioned above. It is now also possible to compare and assign ints and chars to each other.
This commit is contained in:
@@ -427,18 +427,21 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
|
||||
|
||||
@Override
|
||||
public Node visitEqualityExpression(KlangParser.EqualityExpressionContext ctx) {
|
||||
Node lhs = this.visit(ctx.lhs);
|
||||
Node rhs = this.visit(ctx.rhs);
|
||||
int line = ctx.start.getLine();
|
||||
int col = ctx.start.getCharPositionInLine();
|
||||
var lhs = visit(ctx.lhs);
|
||||
var rhs = visit(ctx.rhs);
|
||||
var line = ctx.start.getLine();
|
||||
var col = ctx.start.getCharPositionInLine();
|
||||
|
||||
// Since there are countless combinations of types that are comparable
|
||||
// to each other we use Type::combine to figure out if two types
|
||||
// may be compared to each other
|
||||
try {
|
||||
lhs.type.combine(rhs.type);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage());
|
||||
}
|
||||
|
||||
EqualityExpression result = new EqualityExpression((Expression) lhs, (Expression) rhs);
|
||||
var result = new EqualityExpression((Expression) lhs, (Expression) rhs);
|
||||
result.type = Type.getBooleanType();
|
||||
result.line = line;
|
||||
result.col = col;
|
||||
@@ -447,18 +450,17 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
|
||||
|
||||
@Override
|
||||
public Node visitNotEqualityExpression(KlangParser.NotEqualityExpressionContext ctx) {
|
||||
Node lhs = this.visit(ctx.lhs);
|
||||
Node rhs = this.visit(ctx.rhs);
|
||||
int line = ctx.start.getLine();
|
||||
int col = ctx.start.getCharPositionInLine();
|
||||
var lhs = visit(ctx.lhs);
|
||||
var rhs = visit(ctx.rhs);
|
||||
var line = ctx.start.getLine();
|
||||
var col = ctx.start.getCharPositionInLine();
|
||||
|
||||
try {
|
||||
lhs.type.combine(rhs.type);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage());
|
||||
if (!lhs.type.isPrimitiveType() || !rhs.type.isPrimitiveType()) {
|
||||
var error = "Can only compare primitives.";
|
||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
||||
}
|
||||
|
||||
NotEqualityExpression result = new NotEqualityExpression((Expression) lhs, (Expression) rhs);
|
||||
var result = new NotEqualityExpression((Expression) lhs, (Expression) rhs);
|
||||
result.type = Type.getBooleanType();
|
||||
result.line = line;
|
||||
result.col = col;
|
||||
@@ -467,20 +469,17 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
|
||||
|
||||
@Override
|
||||
public Node visitLessThanExpression(KlangParser.LessThanExpressionContext ctx) {
|
||||
Node lhs = this.visit(ctx.lhs);
|
||||
Node rhs = this.visit(ctx.rhs);
|
||||
int line = ctx.start.getLine();
|
||||
int col = ctx.start.getCharPositionInLine();
|
||||
var lhs = visit(ctx.lhs);
|
||||
var rhs = visit(ctx.rhs);
|
||||
var line = ctx.start.getLine();
|
||||
var col = ctx.start.getCharPositionInLine();
|
||||
|
||||
try {
|
||||
lhs.type.combine(rhs.type);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage());
|
||||
if (!lhs.type.isPrimitiveType() || !rhs.type.isPrimitiveType()) {
|
||||
var error = "Can only compare primitives.";
|
||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
||||
}
|
||||
|
||||
checkNumeric(lhs, rhs, line, col);
|
||||
|
||||
LTExpression result = new LTExpression((Expression) lhs, (Expression) rhs);
|
||||
var result = new LTExpression((Expression) lhs, (Expression) rhs);
|
||||
result.type = Type.getBooleanType();
|
||||
result.line = line;
|
||||
result.col = col;
|
||||
@@ -489,23 +488,17 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
|
||||
|
||||
@Override
|
||||
public Node visitGreaterThanExpression(KlangParser.GreaterThanExpressionContext ctx) {
|
||||
Node lhs = this.visit(ctx.lhs);
|
||||
Node rhs = this.visit(ctx.rhs);
|
||||
int line = ctx.start.getLine();
|
||||
int col = ctx.start.getCharPositionInLine();
|
||||
var lhs = visit(ctx.lhs);
|
||||
var rhs = visit(ctx.rhs);
|
||||
var line = ctx.start.getLine();
|
||||
var col = ctx.start.getCharPositionInLine();
|
||||
|
||||
try {
|
||||
lhs.type.combine(rhs.type);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage());
|
||||
}
|
||||
|
||||
if (!lhs.type.isNumericType() || !rhs.type.isNumericType()) {
|
||||
String error = "Only numeric types are allowed for this expression.";
|
||||
if (!lhs.type.isPrimitiveType() || !rhs.type.isPrimitiveType()) {
|
||||
var error = "Can only compare primitives.";
|
||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
||||
}
|
||||
|
||||
GTExpression result = new GTExpression((Expression) lhs, (Expression) rhs);
|
||||
var result = new GTExpression((Expression) lhs, (Expression) rhs);
|
||||
result.type = Type.getBooleanType();
|
||||
result.line = line;
|
||||
result.col = col;
|
||||
@@ -514,20 +507,17 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
|
||||
|
||||
@Override
|
||||
public Node visitLessThanOrEqualToExpression(KlangParser.LessThanOrEqualToExpressionContext ctx) {
|
||||
Node lhs = this.visit(ctx.lhs);
|
||||
Node rhs = this.visit(ctx.rhs);
|
||||
int line = ctx.start.getLine();
|
||||
int col = ctx.start.getCharPositionInLine();
|
||||
var lhs = visit(ctx.lhs);
|
||||
var rhs = visit(ctx.rhs);
|
||||
var line = ctx.start.getLine();
|
||||
var col = ctx.start.getCharPositionInLine();
|
||||
|
||||
try {
|
||||
lhs.type.combine(rhs.type);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage());
|
||||
if (!lhs.type.isPrimitiveType() || !rhs.type.isPrimitiveType()) {
|
||||
var error = "Can only compare primitives.";
|
||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
||||
}
|
||||
|
||||
checkNumeric(lhs, rhs, line, col);
|
||||
|
||||
LTEExpression result = new LTEExpression((Expression) lhs, (Expression) rhs);
|
||||
var result = new LTEExpression((Expression) lhs, (Expression) rhs);
|
||||
result.type = Type.getBooleanType();
|
||||
result.line = line;
|
||||
result.col = col;
|
||||
@@ -536,20 +526,17 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
|
||||
|
||||
@Override
|
||||
public Node visitGreaterThanOrEqualToExpression(KlangParser.GreaterThanOrEqualToExpressionContext ctx) {
|
||||
Node lhs = this.visit(ctx.lhs);
|
||||
Node rhs = this.visit(ctx.rhs);
|
||||
int line = ctx.start.getLine();
|
||||
int col = ctx.start.getCharPositionInLine();
|
||||
var lhs = visit(ctx.lhs);
|
||||
var rhs = visit(ctx.rhs);
|
||||
var line = ctx.start.getLine();
|
||||
var col = ctx.start.getCharPositionInLine();
|
||||
|
||||
try {
|
||||
lhs.type.combine(rhs.type);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage());
|
||||
if (!lhs.type.isPrimitiveType() || !rhs.type.isPrimitiveType()) {
|
||||
var error = "Can only compare primitives.";
|
||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
||||
}
|
||||
|
||||
checkNumeric(lhs, rhs, line, col);
|
||||
|
||||
GTEExpression result = new GTEExpression((Expression) lhs, (Expression) rhs);
|
||||
var result = new GTEExpression((Expression) lhs, (Expression) rhs);
|
||||
result.type = Type.getBooleanType();
|
||||
result.line = line;
|
||||
result.col = col;
|
||||
|
||||
@@ -20,13 +20,17 @@ public class CharType extends PrimitiveType {
|
||||
|
||||
@Override
|
||||
public Type combine(Type that) {
|
||||
if (!this.equals(that)) {
|
||||
throw new RuntimeException("Type mismatch: cannot combine " + getName() + " and " + getName());
|
||||
if (this.equals(that)) {
|
||||
return this;
|
||||
}
|
||||
|
||||
if (that.equals(Type.getIntegerType())) {
|
||||
return this;
|
||||
}
|
||||
|
||||
throw new RuntimeException("Type mismatch: cannot combine " + getName() + " and " + that.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean valuesEqual(Value a, Value b) {
|
||||
return ((int) a.asChar()) == ((int) b.asChar());
|
||||
|
||||
@@ -35,6 +35,10 @@ public class IntegerType extends NumericType {
|
||||
return Type.getFloatType();
|
||||
}
|
||||
|
||||
if (that.equals(Type.getCharType())) {
|
||||
return Type.getCharType();
|
||||
}
|
||||
|
||||
// Every remaining type will throw a RuntimeException
|
||||
throw new RuntimeException("Type mismatch: cannot combine " + this.getName() + " and " + that.getName());
|
||||
}
|
||||
|
||||
@@ -5,6 +5,44 @@ import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class CharTest {
|
||||
|
||||
@Test
|
||||
void shouldNotThrowIfComparingCharsEquality() {
|
||||
// given
|
||||
var tree = Helper.prepareParser("""
|
||||
function bar(): int {
|
||||
let b: char = 'b';
|
||||
if ('a' == b) {
|
||||
return 1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
bar();
|
||||
""");
|
||||
var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree));
|
||||
|
||||
// when / then
|
||||
assertDoesNotThrow(() -> ctxAnal.visit(tree));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotThrowIfComparingCharsInequality() {
|
||||
// given
|
||||
var tree = Helper.prepareParser("""
|
||||
function bar(): int {
|
||||
let b: char = 'b';
|
||||
if ('a' > b) {
|
||||
return 1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
bar();
|
||||
""");
|
||||
var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree));
|
||||
|
||||
// when / then
|
||||
assertDoesNotThrow(() -> ctxAnal.visit(tree));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotThrowIfCharLiteralIsAssignedToCharVariable() {
|
||||
// given
|
||||
@@ -57,8 +95,9 @@ public class CharTest {
|
||||
void shouldThrowIfCharIsAssignedToOtherTypedVariable() {
|
||||
// given
|
||||
var tree = Helper.prepareParser("""
|
||||
struct foo { f: float; }
|
||||
function bar(): int {
|
||||
let c: int = 'c';
|
||||
let c: foo = 'c';
|
||||
return 1;
|
||||
}
|
||||
bar();
|
||||
@@ -67,14 +106,14 @@ public class CharTest {
|
||||
|
||||
// when / then
|
||||
var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
|
||||
assertEquals("Error in line 2:4 Type mismatch: cannot combine int and char", e.getMessage());
|
||||
assertEquals("Error in line 3:4 Type mismatch: cannot combine foo and char", e.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowWhenReturningCharFromOtherTypedFunction() {
|
||||
// given
|
||||
var tree = Helper.prepareParser("""
|
||||
function bar(): int {
|
||||
function bar(): float {
|
||||
return 'c';
|
||||
}
|
||||
bar();
|
||||
@@ -83,7 +122,7 @@ public class CharTest {
|
||||
|
||||
// when / then
|
||||
var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
|
||||
assertEquals("Error in line 2:4 Type mismatch: cannot combine int and char", e.getMessage());
|
||||
assertEquals("Error in line 2:4 Type mismatch: cannot combine float and char", e.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -109,9 +148,9 @@ public class CharTest {
|
||||
void shouldThrowWhenAssigningCharToOtherTypedStructField() {
|
||||
// given
|
||||
var tree = Helper.prepareParser("""
|
||||
struct foo { a: int; }
|
||||
struct foo { a: bool; }
|
||||
function bar(): void {
|
||||
let x: foo = create foo(1);
|
||||
let x: foo = create foo(false);
|
||||
x.a = 'c';
|
||||
}
|
||||
bar();
|
||||
@@ -120,6 +159,6 @@ public class CharTest {
|
||||
|
||||
// when / then
|
||||
var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
|
||||
assertEquals("Error in line 4:4 Type mismatch: cannot combine int and char", e.getMessage());
|
||||
assertEquals("Error in line 4:4 Type mismatch: cannot combine bool and char", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user