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

View File

@ -2,7 +2,7 @@ package wacc
import parsley.Result import parsley.Result
import parsley.Parsley 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.combinator.{countSome, sepBy}
import parsley.expr.{precedence, SOps, InfixL, InfixN, InfixR, Prefix, Atoms} import parsley.expr.{precedence, SOps, InfixL, InfixN, InfixR, Prefix, Atoms}
import parsley.errors.combinator._ import parsley.errors.combinator._
@ -95,7 +95,9 @@ object parser {
private val `<ident>` = private val `<ident>` =
Ident(ident) | some("*" | "&").verifiedExplain("pointer operators are not allowed") Ident(ident) | some("*" | "&").verifiedExplain("pointer operators are not allowed")
private lazy val `<ident-or-array-elem>` = 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>` <~ "]")) private val `<array-indices>` = ArrayElem(some("[" ~> `<expr>` <~ "]"))
// Types // Types
@ -175,7 +177,15 @@ object parser {
`<rvalue>`.label("valid initial value for variable") `<rvalue>`.label("valid initial value for variable")
) )
// TODO: Can we inline the name of the variable in the message // 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] = private lazy val `<lvalue>`: Parsley[LValue] =
`<pair-elem>` | `<ident-or-array-elem>` `<pair-elem>` | `<ident-or-array-elem>`
private lazy val `<rvalue>`: Parsley[RValue] = private lazy val `<rvalue>`: Parsley[RValue] =

View File

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