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 @Override
public Node visitEqualityExpression(KlangParser.EqualityExpressionContext ctx) { public Node visitEqualityExpression(KlangParser.EqualityExpressionContext ctx) {
Node lhs = this.visit(ctx.lhs); var lhs = visit(ctx.lhs);
Node rhs = this.visit(ctx.rhs); var rhs = visit(ctx.rhs);
int line = ctx.start.getLine(); var line = ctx.start.getLine();
int col = ctx.start.getCharPositionInLine(); 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 { try {
lhs.type.combine(rhs.type); lhs.type.combine(rhs.type);
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage()); 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.type = Type.getBooleanType();
result.line = line; result.line = line;
result.col = col; result.col = col;
@@ -447,18 +450,17 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
@Override @Override
public Node visitNotEqualityExpression(KlangParser.NotEqualityExpressionContext ctx) { public Node visitNotEqualityExpression(KlangParser.NotEqualityExpressionContext ctx) {
Node lhs = this.visit(ctx.lhs); var lhs = visit(ctx.lhs);
Node rhs = this.visit(ctx.rhs); var rhs = visit(ctx.rhs);
int line = ctx.start.getLine(); var line = ctx.start.getLine();
int col = ctx.start.getCharPositionInLine(); var col = ctx.start.getCharPositionInLine();
try { if (!lhs.type.isPrimitiveType() || !rhs.type.isPrimitiveType()) {
lhs.type.combine(rhs.type); var error = "Can only compare primitives.";
} catch (Exception e) { throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage());
} }
NotEqualityExpression result = new NotEqualityExpression((Expression) lhs, (Expression) rhs); var result = new NotEqualityExpression((Expression) lhs, (Expression) rhs);
result.type = Type.getBooleanType(); result.type = Type.getBooleanType();
result.line = line; result.line = line;
result.col = col; result.col = col;
@@ -467,20 +469,17 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
@Override @Override
public Node visitLessThanExpression(KlangParser.LessThanExpressionContext ctx) { public Node visitLessThanExpression(KlangParser.LessThanExpressionContext ctx) {
Node lhs = this.visit(ctx.lhs); var lhs = visit(ctx.lhs);
Node rhs = this.visit(ctx.rhs); var rhs = visit(ctx.rhs);
int line = ctx.start.getLine(); var line = ctx.start.getLine();
int col = ctx.start.getCharPositionInLine(); var col = ctx.start.getCharPositionInLine();
try { if (!lhs.type.isPrimitiveType() || !rhs.type.isPrimitiveType()) {
lhs.type.combine(rhs.type); var error = "Can only compare primitives.";
} catch (Exception e) { throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage());
} }
checkNumeric(lhs, rhs, line, col); var result = new LTExpression((Expression) lhs, (Expression) rhs);
LTExpression result = new LTExpression((Expression) lhs, (Expression) rhs);
result.type = Type.getBooleanType(); result.type = Type.getBooleanType();
result.line = line; result.line = line;
result.col = col; result.col = col;
@@ -489,23 +488,17 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
@Override @Override
public Node visitGreaterThanExpression(KlangParser.GreaterThanExpressionContext ctx) { public Node visitGreaterThanExpression(KlangParser.GreaterThanExpressionContext ctx) {
Node lhs = this.visit(ctx.lhs); var lhs = visit(ctx.lhs);
Node rhs = this.visit(ctx.rhs); var rhs = visit(ctx.rhs);
int line = ctx.start.getLine(); var line = ctx.start.getLine();
int col = ctx.start.getCharPositionInLine(); var col = ctx.start.getCharPositionInLine();
try { if (!lhs.type.isPrimitiveType() || !rhs.type.isPrimitiveType()) {
lhs.type.combine(rhs.type); var error = "Can only compare primitives.";
} 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.";
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error); 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.type = Type.getBooleanType();
result.line = line; result.line = line;
result.col = col; result.col = col;
@@ -514,20 +507,17 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
@Override @Override
public Node visitLessThanOrEqualToExpression(KlangParser.LessThanOrEqualToExpressionContext ctx) { public Node visitLessThanOrEqualToExpression(KlangParser.LessThanOrEqualToExpressionContext ctx) {
Node lhs = this.visit(ctx.lhs); var lhs = visit(ctx.lhs);
Node rhs = this.visit(ctx.rhs); var rhs = visit(ctx.rhs);
int line = ctx.start.getLine(); var line = ctx.start.getLine();
int col = ctx.start.getCharPositionInLine(); var col = ctx.start.getCharPositionInLine();
try { if (!lhs.type.isPrimitiveType() || !rhs.type.isPrimitiveType()) {
lhs.type.combine(rhs.type); var error = "Can only compare primitives.";
} catch (Exception e) { throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage());
} }
checkNumeric(lhs, rhs, line, col); var result = new LTEExpression((Expression) lhs, (Expression) rhs);
LTEExpression result = new LTEExpression((Expression) lhs, (Expression) rhs);
result.type = Type.getBooleanType(); result.type = Type.getBooleanType();
result.line = line; result.line = line;
result.col = col; result.col = col;
@@ -536,20 +526,17 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
@Override @Override
public Node visitGreaterThanOrEqualToExpression(KlangParser.GreaterThanOrEqualToExpressionContext ctx) { public Node visitGreaterThanOrEqualToExpression(KlangParser.GreaterThanOrEqualToExpressionContext ctx) {
Node lhs = this.visit(ctx.lhs); var lhs = visit(ctx.lhs);
Node rhs = this.visit(ctx.rhs); var rhs = visit(ctx.rhs);
int line = ctx.start.getLine(); var line = ctx.start.getLine();
int col = ctx.start.getCharPositionInLine(); var col = ctx.start.getCharPositionInLine();
try { if (!lhs.type.isPrimitiveType() || !rhs.type.isPrimitiveType()) {
lhs.type.combine(rhs.type); var error = "Can only compare primitives.";
} catch (Exception e) { throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage());
} }
checkNumeric(lhs, rhs, line, col); var result = new GTEExpression((Expression) lhs, (Expression) rhs);
GTEExpression result = new GTEExpression((Expression) lhs, (Expression) rhs);
result.type = Type.getBooleanType(); result.type = Type.getBooleanType();
result.line = line; result.line = line;
result.col = col; result.col = col;

View File

@@ -20,11 +20,15 @@ public class CharType extends PrimitiveType {
@Override @Override
public Type combine(Type that) { public Type combine(Type that) {
if (!this.equals(that)) { if (this.equals(that)) {
throw new RuntimeException("Type mismatch: cannot combine " + getName() + " and " + getName()); return this;
} }
return this; if (that.equals(Type.getIntegerType())) {
return this;
}
throw new RuntimeException("Type mismatch: cannot combine " + getName() + " and " + that.getName());
} }
@Override @Override

View File

@@ -35,6 +35,10 @@ public class IntegerType extends NumericType {
return Type.getFloatType(); return Type.getFloatType();
} }
if (that.equals(Type.getCharType())) {
return Type.getCharType();
}
// Every remaining type will throw a RuntimeException // Every remaining type will throw a RuntimeException
throw new RuntimeException("Type mismatch: cannot combine " + this.getName() + " and " + that.getName()); 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 { 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 @Test
void shouldNotThrowIfCharLiteralIsAssignedToCharVariable() { void shouldNotThrowIfCharLiteralIsAssignedToCharVariable() {
// given // given
@@ -57,8 +95,9 @@ public class CharTest {
void shouldThrowIfCharIsAssignedToOtherTypedVariable() { void shouldThrowIfCharIsAssignedToOtherTypedVariable() {
// given // given
var tree = Helper.prepareParser(""" var tree = Helper.prepareParser("""
struct foo { f: float; }
function bar(): int { function bar(): int {
let c: int = 'c'; let c: foo = 'c';
return 1; return 1;
} }
bar(); bar();
@@ -67,14 +106,14 @@ public class CharTest {
// when / then // when / then
var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); 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 @Test
void shouldThrowWhenReturningCharFromOtherTypedFunction() { void shouldThrowWhenReturningCharFromOtherTypedFunction() {
// given // given
var tree = Helper.prepareParser(""" var tree = Helper.prepareParser("""
function bar(): int { function bar(): float {
return 'c'; return 'c';
} }
bar(); bar();
@@ -83,7 +122,7 @@ public class CharTest {
// when / then // when / then
var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); 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 @Test
@@ -109,9 +148,9 @@ public class CharTest {
void shouldThrowWhenAssigningCharToOtherTypedStructField() { void shouldThrowWhenAssigningCharToOtherTypedStructField() {
// given // given
var tree = Helper.prepareParser(""" var tree = Helper.prepareParser("""
struct foo { a: int; } struct foo { a: bool; }
function bar(): void { function bar(): void {
let x: foo = create foo(1); let x: foo = create foo(false);
x.a = 'c'; x.a = 'c';
} }
bar(); bar();
@@ -120,6 +159,6 @@ public class CharTest {
// when / then // when / then
var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree)); 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());
} }
} }