From e87e61b1520d1b6a4042e26f1e089f2f5ac05c77 Mon Sep 17 00:00:00 2001 From: Gleb Koval Date: Fri, 7 Feb 2025 17:16:23 +0000 Subject: [PATCH 1/6] feat: explicitly disallow non-assigned direct function calls --- src/main/wacc/parser.scala | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/wacc/parser.scala b/src/main/wacc/parser.scala index 7d76969..d8b52b4 100644 --- a/src/main/wacc/parser.scala +++ b/src/main/wacc/parser.scala @@ -175,7 +175,15 @@ object parser { ``.label("valid initial value for variable") ) // TODO: Can we inline the name of the variable in the message - | Assign(`` <~ "=", ``) + | Assign( + `` <~ ("=" | "(".verifiedExplain( + "function calls must use the 'call' keyword and the result must be assigned to a variable" + )), + `` + ) | + ("call" ~> ``).verifiedExplain( + "function calls' results must be assigned to a variable" + ) private lazy val ``: Parsley[LValue] = `` | `` private lazy val ``: Parsley[RValue] = From b5e72660b71f00d34aed399b705e74929979cdf5 Mon Sep 17 00:00:00 2001 From: Gleb Koval Date: Fri, 7 Feb 2025 17:19:16 +0000 Subject: [PATCH 2/6] fix: make free error message more clear --- src/main/wacc/typeChecker.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/wacc/typeChecker.scala b/src/main/wacc/typeChecker.scala index 8c01379..c0ed0c1 100644 --- a/src/main/wacc/typeChecker.scala +++ b/src/main/wacc/typeChecker.scala @@ -125,7 +125,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 +163,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") From 3d2725be8d9955a42dd3b57e4b5f45ce22b49aec Mon Sep 17 00:00:00 2001 From: Barf-Vader <47476490+Barf-Vader@users.noreply.github.com> Date: Fri, 7 Feb 2025 17:22:54 +0000 Subject: [PATCH 3/6] feat: catch indirect function calls --- src/main/wacc/parser.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/wacc/parser.scala b/src/main/wacc/parser.scala index d8b52b4..ddb5987 100644 --- a/src/main/wacc/parser.scala +++ b/src/main/wacc/parser.scala @@ -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) | some("*" | "&").verifiedExplain("pointer operators are not allowed") private lazy val `` = - `` <**> (`` identity) + (`` <~ ("(".verifiedExplain( + "functions can only be called using 'call' keyword" + ) | unit)) <**> (`` identity) private val `` = ArrayElem(some("[" ~> `` <~ "]")) // Types From 8a7b37e05f144c3ee422d9572b1ea85d10ede2ea Mon Sep 17 00:00:00 2001 From: Gleb Koval Date: Fri, 7 Feb 2025 17:32:24 +0000 Subject: [PATCH 4/6] fix: check both sides are unknown for assign error message --- src/main/wacc/typeChecker.scala | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/main/wacc/typeChecker.scala b/src/main/wacc/typeChecker.scala index c0ed0c1..2105ecb 100644 --- a/src/main/wacc/typeChecker.scala +++ b/src/main/wacc/typeChecker.scala @@ -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) => From 4d25d7a73005a734be48973fa9d41bb1a9632c8a Mon Sep 17 00:00:00 2001 From: Gleb Koval Date: Fri, 7 Feb 2025 18:02:31 +0000 Subject: [PATCH 5/6] fix: make int binary operators and char binary operators errors consistent --- src/main/wacc/typeChecker.scala | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/main/wacc/typeChecker.scala b/src/main/wacc/typeChecker.scala index 2105ecb..80eda2e 100644 --- a/src/main/wacc/typeChecker.scala +++ b/src/main/wacc/typeChecker.scala @@ -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( - KnownType.Int, - KnownType.Char, - s"${op.name} operator must be applied to an int or char" - ) - ) - checkValue( - op.y, - Constraint.Is(xTy, s"${op.name} operator must be applied to values of the same type") + val xConstraint = Constraint.IsEither( + KnownType.Int, + KnownType.Char, + s"${op.name} operator must be applied to an int or char" ) + 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") From 5ea3ca5a03b318383b9e6dafc447abf39ba58966 Mon Sep 17 00:00:00 2001 From: Gleb Koval Date: Fri, 7 Feb 2025 18:09:18 +0000 Subject: [PATCH 6/6] fix: display function type on incorrect number of args --- src/main/wacc/Error.scala | 11 +++++++---- src/main/wacc/typeChecker.scala | 4 ++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main/wacc/Error.scala b/src/main/wacc/Error.scala index fec2662..0f3c01d 100644 --- a/src/main/wacc/Error.scala +++ b/src/main/wacc/Error.scala @@ -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") diff --git a/src/main/wacc/typeChecker.scala b/src/main/wacc/typeChecker.scala index 80eda2e..c766905 100644 --- a/src/main/wacc/typeChecker.scala +++ b/src/main/wacc/typeChecker.scala @@ -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}"))