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:
commit
585ba6958a
@ -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")
|
||||||
|
@ -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] =
|
||||||
|
@ -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")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user