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:
2023-03-24 00:17:38 +01:00
parent 2aff9b3d0d
commit 259ac49981
4 changed files with 104 additions and 70 deletions

View File

@@ -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;

View File

@@ -20,11 +20,15 @@ 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;
}
return this;
if (that.equals(Type.getIntegerType())) {
return this;
}
throw new RuntimeException("Type mismatch: cannot combine " + getName() + " and " + that.getName());
}
@Override

View File

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

View File

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