From 68e4762b37480dde79c82c8f25c9de2a1e3aa6b1 Mon Sep 17 00:00:00 2001 From: Gleb Koval Date: Fri, 7 Feb 2025 14:16:20 +0000 Subject: [PATCH 1/5] feat: show expected and got types for TypeMismatch --- src/main/wacc/Error.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/wacc/Error.scala b/src/main/wacc/Error.scala index 89e03d9..ba810f5 100644 --- a/src/main/wacc/Error.scala +++ b/src/main/wacc/Error.scala @@ -35,7 +35,7 @@ def printError(error: Error)(using errorContent: String): Unit = { highlight(pos, 1) case Error.TypeMismatch(pos, expected, got, msg) => printPosition(pos) - println(msg) + println(s"Type mismatch: $msg\nExpected: $expected\nGot: $got") highlight(pos, 1) case Error.SemanticError(pos, msg) => printPosition(pos) From 319fa606d91f3f4988b3b1f66d8b5c520897b29f Mon Sep 17 00:00:00 2001 From: Gleb Koval Date: Fri, 7 Feb 2025 14:16:50 +0000 Subject: [PATCH 2/5] fix: foldLeft when type checking, rather than foldRight which is unintuitive --- src/main/wacc/typeChecker.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/wacc/typeChecker.scala b/src/main/wacc/typeChecker.scala index 38b34e3..f0f523b 100644 --- a/src/main/wacc/typeChecker.scala +++ b/src/main/wacc/typeChecker.scala @@ -159,7 +159,7 @@ object typeChecker { ctx.typeOf(id).satisfies(constraint, id.pos) case ArrayElem(id, indices) => val arrayTy = ctx.typeOf(id) - val elemTy = indices.toList.foldRight(arrayTy) { (elem, acc) => + val elemTy = indices.foldLeft(arrayTy) { (acc, elem) => checkValue(elem, Constraint.Is(KnownType.Int, "array index must be an int")) acc match { case KnownType.Array(innerTy) => innerTy @@ -173,10 +173,10 @@ object typeChecker { case Parens(expr) => checkValue(expr, constraint) case l @ ArrayLiter(elems) => KnownType - .Array(elems.foldRight[SemType](?) { case (elem, acc) => + .Array(elems.foldLeft[SemType](?) { case (acc, elem) => checkValue( elem, - Constraint.IsSymmetricCompatible(acc, "array elements must have the same type") + Constraint.IsSymmetricCompatible(acc, s"array elements must have the same type") ) }) .satisfies(constraint, l.pos) From 343029984708c52ff56652dcc38a112079f909b1 Mon Sep 17 00:00:00 2001 From: Gleb Koval Date: Fri, 7 Feb 2025 14:22:36 +0000 Subject: [PATCH 3/5] feat: include operator name in type errors --- src/main/wacc/ast.scala | 38 +++++++++++++++++++++------------ src/main/wacc/typeChecker.scala | 16 +++++++++----- 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/src/main/wacc/ast.scala b/src/main/wacc/ast.scala index 8ea99d7..f885843 100644 --- a/src/main/wacc/ast.scala +++ b/src/main/wacc/ast.scala @@ -60,35 +60,45 @@ object ast { object Chr extends ParserBridgePos1[Expr6, Chr] // Binary operators - sealed trait BinaryOp extends Expr { + sealed trait BinaryOp(val name: String) extends Expr { val x: Expr val y: Expr } - case class Add(x: Expr4, y: Expr5)(val pos: Position) extends Expr4 with BinaryOp + case class Add(x: Expr4, y: Expr5)(val pos: Position) extends Expr4 with BinaryOp("addition") object Add extends ParserBridgePos2[Expr4, Expr5, Add] - case class Sub(x: Expr4, y: Expr5)(val pos: Position) extends Expr4 with BinaryOp + case class Sub(x: Expr4, y: Expr5)(val pos: Position) extends Expr4 with BinaryOp("subtraction") object Sub extends ParserBridgePos2[Expr4, Expr5, Sub] - case class Mul(x: Expr5, y: Expr6)(val pos: Position) extends Expr5 with BinaryOp + case class Mul(x: Expr5, y: Expr6)(val pos: Position) + extends Expr5 + with BinaryOp("multiplication") object Mul extends ParserBridgePos2[Expr5, Expr6, Mul] - case class Div(x: Expr5, y: Expr6)(val pos: Position) extends Expr5 with BinaryOp + case class Div(x: Expr5, y: Expr6)(val pos: Position) extends Expr5 with BinaryOp("division") object Div extends ParserBridgePos2[Expr5, Expr6, Div] - case class Mod(x: Expr5, y: Expr6)(val pos: Position) extends Expr5 with BinaryOp + case class Mod(x: Expr5, y: Expr6)(val pos: Position) extends Expr5 with BinaryOp("modulus") object Mod extends ParserBridgePos2[Expr5, Expr6, Mod] - case class Greater(x: Expr4, y: Expr4)(val pos: Position) extends Expr3 with BinaryOp + case class Greater(x: Expr4, y: Expr4)(val pos: Position) + extends Expr3 + with BinaryOp("strictly greater than") object Greater extends ParserBridgePos2[Expr4, Expr4, Greater] - case class GreaterEq(x: Expr4, y: Expr4)(val pos: Position) extends Expr3 with BinaryOp + case class GreaterEq(x: Expr4, y: Expr4)(val pos: Position) + extends Expr3 + with BinaryOp("greater than or equal to") object GreaterEq extends ParserBridgePos2[Expr4, Expr4, GreaterEq] - case class Less(x: Expr4, y: Expr4)(val pos: Position) extends Expr3 with BinaryOp + case class Less(x: Expr4, y: Expr4)(val pos: Position) + extends Expr3 + with BinaryOp("strictly less than") object Less extends ParserBridgePos2[Expr4, Expr4, Less] - case class LessEq(x: Expr4, y: Expr4)(val pos: Position) extends Expr3 with BinaryOp + case class LessEq(x: Expr4, y: Expr4)(val pos: Position) + extends Expr3 + with BinaryOp("less than or equal to") object LessEq extends ParserBridgePos2[Expr4, Expr4, LessEq] - case class Eq(x: Expr3, y: Expr3)(val pos: Position) extends Expr2 with BinaryOp + case class Eq(x: Expr3, y: Expr3)(val pos: Position) extends Expr2 with BinaryOp("equality") object Eq extends ParserBridgePos2[Expr3, Expr3, Eq] - case class Neq(x: Expr3, y: Expr3)(val pos: Position) extends Expr2 with BinaryOp + case class Neq(x: Expr3, y: Expr3)(val pos: Position) extends Expr2 with BinaryOp("inequality") object Neq extends ParserBridgePos2[Expr3, Expr3, Neq] - case class And(x: Expr2, y: Expr1)(val pos: Position) extends Expr1 with BinaryOp + case class And(x: Expr2, y: Expr1)(val pos: Position) extends Expr1 with BinaryOp("logical and") object And extends ParserBridgePos2[Expr2, Expr1, And] - case class Or(x: Expr1, y: Expr)(val pos: Position) extends Expr with BinaryOp + case class Or(x: Expr1, y: Expr)(val pos: Position) extends Expr with BinaryOp("logical or") object Or extends ParserBridgePos2[Expr1, Expr, Or] // Types diff --git a/src/main/wacc/typeChecker.scala b/src/main/wacc/typeChecker.scala index f0f523b..d9ebecc 100644 --- a/src/main/wacc/typeChecker.scala +++ b/src/main/wacc/typeChecker.scala @@ -233,13 +233,16 @@ object typeChecker { // Binary operators case op: (Add | Sub | Mul | Div | Mod) => - val operand = Constraint.Is(KnownType.Int, "binary operator must be applied to an int") + val operand = Constraint.Is(KnownType.Int, s"${op.name} operator must be applied to an int") checkValue(op.x, operand) checkValue(op.y, operand) KnownType.Int.satisfies(constraint, op.pos) case op: (Eq | Neq) => val xTy = checkValue(op.x, Constraint.Unconstrained) - checkValue(op.y, Constraint.Is(xTy, "equality must be applied to values of the same type")) + checkValue( + op.y, + Constraint.Is(xTy, s"${op.name} operator must be applied to values of the same type") + ) KnownType.Bool.satisfies(constraint, op.pos) case op: (Less | LessEq | Greater | GreaterEq) => val xTy = checkValue( @@ -247,13 +250,16 @@ object typeChecker { Constraint.IsEither( KnownType.Int, KnownType.Char, - "comparison must be applied to an int or char" + s"${op.name} operator must be applied to an int or char" ) ) - checkValue(op.y, Constraint.Is(xTy, "comparison must be applied to values of the same type")) + checkValue( + op.y, + Constraint.Is(xTy, s"${op.name} operator must be applied to values of the same type") + ) KnownType.Bool.satisfies(constraint, op.pos) case op: (And | Or) => - val operand = Constraint.Is(KnownType.Bool, "logical operator must be applied to a bool") + val operand = Constraint.Is(KnownType.Bool, s"${op.name} operator must be applied to a bool") checkValue(op.x, operand) checkValue(op.y, operand) KnownType.Bool.satisfies(constraint, op.pos) From 88ddca2b987ffdf395f3cf834baa66751dd2352f Mon Sep 17 00:00:00 2001 From: Gleb Koval Date: Fri, 7 Feb 2025 14:24:16 +0000 Subject: [PATCH 4/5] feat: do not include `?` type in error messages --- src/main/wacc/types.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/wacc/types.scala b/src/main/wacc/types.scala index 41d4124..549d8a1 100644 --- a/src/main/wacc/types.scala +++ b/src/main/wacc/types.scala @@ -9,9 +9,11 @@ object types { case KnownType.Bool => "bool" case KnownType.Char => "char" case KnownType.String => "string" + case KnownType.Array(?) => "array" case KnownType.Array(elem) => s"$elem[]" + case KnownType.Pair(?, ?) => "pair" case KnownType.Pair(left, right) => s"pair($left, $right)" - case ? => "?" + case ? => "" } } From ba1b7d67c78e00e38e21db22900c4ca0265625a2 Mon Sep 17 00:00:00 2001 From: Gleb Koval Date: Fri, 7 Feb 2025 14:32:15 +0000 Subject: [PATCH 5/5] fix: return proper type in non-array index error (instead of `?`) --- src/main/wacc/typeChecker.scala | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/wacc/typeChecker.scala b/src/main/wacc/typeChecker.scala index d9ebecc..8c01379 100644 --- a/src/main/wacc/typeChecker.scala +++ b/src/main/wacc/typeChecker.scala @@ -159,17 +159,18 @@ object typeChecker { ctx.typeOf(id).satisfies(constraint, id.pos) case ArrayElem(id, indices) => val arrayTy = ctx.typeOf(id) - val elemTy = indices.foldLeft(arrayTy) { (acc, elem) => + val elemTy = indices.foldLeftM(arrayTy) { (acc, elem) => checkValue(elem, Constraint.Is(KnownType.Int, "array index must be an int")) acc match { - case KnownType.Array(innerTy) => innerTy - case _ => + case KnownType.Array(innerTy) => Some(innerTy) + case nonArrayTy => ctx.error( Error.TypeMismatch(elem.pos, KnownType.Array(?), acc, "cannot index into a non-array") ) + None } } - elemTy.satisfies(constraint, id.pos) + elemTy.getOrElse(?).satisfies(constraint, id.pos) case Parens(expr) => checkValue(expr, constraint) case l @ ArrayLiter(elems) => KnownType