feat: improve semantic errors

Merge request lab2425_spring/WACC_37!15
This commit is contained in:
Gleb Koval 2025-02-07 15:07:35 +00:00
commit ba5445a4c5
4 changed files with 46 additions and 27 deletions

View File

@ -35,7 +35,7 @@ def printError(error: Error)(using errorContent: String): Unit = {
highlight(pos, 1) highlight(pos, 1)
case Error.TypeMismatch(pos, expected, got, msg) => case Error.TypeMismatch(pos, expected, got, msg) =>
printPosition(pos) printPosition(pos)
println(msg) println(s"Type mismatch: $msg\nExpected: $expected\nGot: $got")
highlight(pos, 1) highlight(pos, 1)
case Error.SemanticError(pos, msg) => case Error.SemanticError(pos, msg) =>
printPosition(pos) printPosition(pos)

View File

@ -60,35 +60,45 @@ object ast {
object Chr extends ParserBridgePos1[Expr6, Chr] object Chr extends ParserBridgePos1[Expr6, Chr]
// Binary operators // Binary operators
sealed trait BinaryOp extends Expr { sealed trait BinaryOp(val name: String) extends Expr {
val x: Expr val x: Expr
val y: 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] 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] 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] 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] 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] 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] 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] 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] 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] 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] 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] 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] 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] object Or extends ParserBridgePos2[Expr1, Expr, Or]
// Types // Types

View File

@ -159,24 +159,25 @@ object typeChecker {
ctx.typeOf(id).satisfies(constraint, id.pos) ctx.typeOf(id).satisfies(constraint, id.pos)
case ArrayElem(id, indices) => case ArrayElem(id, indices) =>
val arrayTy = ctx.typeOf(id) val arrayTy = ctx.typeOf(id)
val elemTy = indices.toList.foldRight(arrayTy) { (elem, acc) => val elemTy = indices.foldLeftM(arrayTy) { (acc, elem) =>
checkValue(elem, Constraint.Is(KnownType.Int, "array index must be an int")) checkValue(elem, Constraint.Is(KnownType.Int, "array index must be an int"))
acc match { acc match {
case KnownType.Array(innerTy) => innerTy case KnownType.Array(innerTy) => Some(innerTy)
case _ => case nonArrayTy =>
ctx.error( ctx.error(
Error.TypeMismatch(elem.pos, KnownType.Array(?), acc, "cannot index into a non-array") 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 Parens(expr) => checkValue(expr, constraint)
case l @ ArrayLiter(elems) => case l @ ArrayLiter(elems) =>
KnownType KnownType
.Array(elems.foldRight[SemType](?) { case (elem, acc) => .Array(elems.foldLeft[SemType](?) { case (acc, elem) =>
checkValue( checkValue(
elem, 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) .satisfies(constraint, l.pos)
@ -233,13 +234,16 @@ object typeChecker {
// Binary operators // Binary operators
case op: (Add | Sub | Mul | Div | Mod) => 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.x, operand)
checkValue(op.y, operand) checkValue(op.y, operand)
KnownType.Int.satisfies(constraint, op.pos) KnownType.Int.satisfies(constraint, op.pos)
case op: (Eq | Neq) => case op: (Eq | Neq) =>
val xTy = checkValue(op.x, Constraint.Unconstrained) 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) KnownType.Bool.satisfies(constraint, op.pos)
case op: (Less | LessEq | Greater | GreaterEq) => case op: (Less | LessEq | Greater | GreaterEq) =>
val xTy = checkValue( val xTy = checkValue(
@ -247,13 +251,16 @@ object typeChecker {
Constraint.IsEither( Constraint.IsEither(
KnownType.Int, KnownType.Int,
KnownType.Char, 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) KnownType.Bool.satisfies(constraint, op.pos)
case op: (And | Or) => 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.x, operand)
checkValue(op.y, operand) checkValue(op.y, operand)
KnownType.Bool.satisfies(constraint, op.pos) KnownType.Bool.satisfies(constraint, op.pos)

View File

@ -9,9 +9,11 @@ object types {
case KnownType.Bool => "bool" case KnownType.Bool => "bool"
case KnownType.Char => "char" case KnownType.Char => "char"
case KnownType.String => "string" case KnownType.String => "string"
case KnownType.Array(?) => "array"
case KnownType.Array(elem) => s"$elem[]" case KnownType.Array(elem) => s"$elem[]"
case KnownType.Pair(?, ?) => "pair"
case KnownType.Pair(left, right) => s"pair($left, $right)" case KnownType.Pair(left, right) => s"pair($left, $right)"
case ? => "?" case ? => "<unknown-type>"
} }
} }