feat: reduce appearances of unknown-type, catch illegal function calls

Merge request lab2425_spring/WACC_37!19

Co-authored-by: Barf-Vader <47476490+Barf-Vader@users.noreply.github.com>
This commit is contained in:
Gleb Koval 2025-02-07 18:14:33 +00:00
commit 585ba6958a
3 changed files with 42 additions and 30 deletions

View File

@ -10,7 +10,7 @@ enum Error {
case UndeclaredVariable(ident: ast.Ident)
case UndefinedFunction(ident: ast.Ident)
case FunctionParamsMismatch(pos: Position, expected: Int, got: Int)
case FunctionParamsMismatch(ident: ast.Ident, expected: Int, got: Int, funcType: FuncType)
case SemanticError(pos: Position, msg: String)
case TypeMismatch(pos: Position, expected: SemType, got: SemType, msg: String)
case InternalError(pos: Position, msg: String)
@ -38,10 +38,13 @@ def printError(error: Error)(using errorContent: String): Unit = {
printPosition(ident.pos)
println(s"Undefined function ${ident.v}")
highlight(ident.pos, ident.v.length)
case Error.FunctionParamsMismatch(pos, expected, got) =>
printPosition(pos)
case Error.FunctionParamsMismatch(id, expected, got, funcType) =>
printPosition(id.pos)
println(s"Function expects $expected parameters, got $got")
highlight(pos, 1)
println(
s"(function ${id.v} has type (${funcType.params.mkString(", ")}) -> ${funcType.returnType})"
)
highlight(id.pos, 1)
case Error.TypeMismatch(pos, expected, got, msg) =>
printPosition(pos)
println(s"Type mismatch: $msg\nExpected: $expected\nGot: $got")

View File

@ -2,7 +2,7 @@ package wacc
import parsley.Result
import parsley.Parsley
import parsley.Parsley.{atomic, many, notFollowedBy, pure}
import parsley.Parsley.{atomic, many, notFollowedBy, pure, unit}
import parsley.combinator.{countSome, sepBy}
import parsley.expr.{precedence, SOps, InfixL, InfixN, InfixR, Prefix, Atoms}
import parsley.errors.combinator._
@ -95,7 +95,9 @@ object parser {
private val `<ident>` =
Ident(ident) | some("*" | "&").verifiedExplain("pointer operators are not allowed")
private lazy val `<ident-or-array-elem>` =
`<ident>` <**> (`<array-indices>` </> identity)
(`<ident>` <~ ("(".verifiedExplain(
"functions can only be called using 'call' keyword"
) | unit)) <**> (`<array-indices>` </> identity)
private val `<array-indices>` = ArrayElem(some("[" ~> `<expr>` <~ "]"))
// Types
@ -175,7 +177,15 @@ object parser {
`<rvalue>`.label("valid initial value for variable")
)
// TODO: Can we inline the name of the variable in the message
| Assign(`<lvalue>` <~ "=", `<rvalue>`)
| Assign(
`<lvalue>` <~ ("=" | "(".verifiedExplain(
"function calls must use the 'call' keyword and the result must be assigned to a variable"
)),
`<rvalue>`
) |
("call" ~> `<ident>`).verifiedExplain(
"function calls' results must be assigned to a variable"
)
private lazy val `<lvalue>`: Parsley[LValue] =
`<pair-elem>` | `<ident-or-array-elem>`
private lazy val `<rvalue>`: Parsley[RValue] =

View File

@ -95,28 +95,27 @@ object typeChecker {
)
case Assign(lhs, rhs) =>
val lhsTy = checkValue(lhs, Constraint.Unconstrained)
checkValue(rhs, Constraint.Is(lhsTy, s"assignment must have type $lhsTy")) match {
case ? =>
(lhsTy, checkValue(rhs, Constraint.Is(lhsTy, s"assignment must have type $lhsTy"))) match {
case (?, ?) =>
ctx.error(
Error.SemanticError(lhs.pos, "assignment with both sides of unknown type is illegal")
)
case _ => ()
}
case Read(lhs) =>
val lhsTy = checkValue(lhs, Constraint.Unconstrained)
lhsTy match {
case Read(dest) =>
checkValue(dest, Constraint.Unconstrained) match {
case ? =>
ctx.error(
Error.SemanticError(lhs.pos, "cannot read into a destination with an unknown type")
Error.SemanticError(dest.pos, "cannot read into a destination with an unknown type")
)
case _ =>
lhsTy.satisfies(
case destTy =>
destTy.satisfies(
Constraint.IsEither(
KnownType.Int,
KnownType.Char,
"read must be applied to an int or char"
),
lhs.pos
dest.pos
)
}
case Free(lhs) =>
@ -125,7 +124,7 @@ object typeChecker {
Constraint.IsEither(
KnownType.Array(?),
KnownType.Pair(?, ?),
"free must be an array or pair"
"free must be applied to an array or pair"
)
)
case Return(expr) =>
@ -163,6 +162,7 @@ object typeChecker {
checkValue(elem, Constraint.Is(KnownType.Int, "array index must be an int"))
acc match {
case KnownType.Array(innerTy) => Some(innerTy)
case ? => Some(?)
case nonArrayTy =>
ctx.error(
Error.TypeMismatch(elem.pos, KnownType.Array(?), acc, "cannot index into a non-array")
@ -189,9 +189,9 @@ object typeChecker {
)
.satisfies(constraint, l.pos)
case Call(id, args) =>
val FuncType(retTy, paramTys) = ctx.funcType(id)
val funcTy @ FuncType(retTy, paramTys) = ctx.funcType(id)
if (args.length != paramTys.length) {
ctx.error(Error.FunctionParamsMismatch(id.pos, paramTys.length, args.length))
ctx.error(Error.FunctionParamsMismatch(id, paramTys.length, args.length, funcTy))
}
args.zip(paramTys).foreach { case (arg, paramTy) =>
checkValue(arg, Constraint.Is(paramTy, s"argument type mismatch in function ${id.v}"))
@ -246,18 +246,17 @@ object typeChecker {
)
KnownType.Bool.satisfies(constraint, op.pos)
case op: (Less | LessEq | Greater | GreaterEq) =>
val xTy = checkValue(
op.x,
Constraint.IsEither(
val xConstraint = Constraint.IsEither(
KnownType.Int,
KnownType.Char,
s"${op.name} operator must be applied to an int or char"
)
)
checkValue(
op.y,
val yConstraint = checkValue(op.x, xConstraint) match {
case ? => xConstraint
case xTy =>
Constraint.Is(xTy, s"${op.name} operator must be applied to values of the same type")
)
}
checkValue(op.y, yConstraint)
KnownType.Bool.satisfies(constraint, op.pos)
case op: (And | Or) =>
val operand = Constraint.Is(KnownType.Bool, s"${op.name} operator must be applied to a bool")