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
|
@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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user