Skip to content

Commit 4234b43

Browse files
committed
impl check missing return statement in block
1 parent 351651f commit 4234b43

File tree

7 files changed

+73
-11
lines changed

7 files changed

+73
-11
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
*** Error at (2,16): missing return statement: control reaches end of non-void block
2+
*** Error at (4,21): missing return statement: control reaches end of non-void block
3+
*** Error at (10,24): missing return statement: control reaches end of non-void block

TestCases/S2/returnerror.decaf

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
class Main {
2+
int fun1() { }
3+
4+
int fun2(int x) {
5+
while (true) {
6+
if (x == 1) return 3;
7+
}
8+
}
9+
10+
int abs_fun(int x) {
11+
if (x < 0)
12+
return -x;
13+
else if (x >= 0)
14+
return x;
15+
}
16+
17+
static void main() {
18+
}
19+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package decaf.annot
2+
3+
sealed trait Flag extends Annot
4+
5+
case object Yes extends Flag
6+
7+
case object No extends Flag
8+
9+
object FlagImplicit {
10+
11+
implicit class FlagAnnotatedHasFlag(self: Annotated[Flag]) {
12+
def flag: Flag = self.annot
13+
14+
def yes: Boolean = self.annot match {
15+
case Yes => true
16+
case No => false
17+
}
18+
19+
def no: Boolean = self.annot match {
20+
case Yes => false
21+
case No => true
22+
}
23+
}
24+
25+
}

src/main/scala/decaf/error/Errors.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ class BadPrintArgError(k: Int, actual: Type, pos: Pos)
6969
class BadReturnTypeError(expected: Type, actual: Type, pos: Pos)
7070
extends Error(s"incompatible return: $actual given, $expected expected", pos)
7171

72+
class MissingReturnError(pos: Pos)
73+
extends Error("missing return statement: control reaches end of non-void block", pos)
74+
7275
class IncompatUnOpError(op: String, exprType: Type, pos: Pos)
7376
extends Error(s"incompatible operand: $op $exprType", pos)
7477

src/main/scala/decaf/tree/TypedTree.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ object TypedTree extends TreeTmpl {
1818
type LocalVarAnnot = LocalVarSymbol
1919
type MethodAnnot = MethodSymbol
2020
type TypeLitAnnot = Type
21-
type StmtAnnot = SyntaxTree.No
21+
type StmtAnnot = Flag
2222
type BlockAnnot = LocalScope
2323
type ExprAnnot = Type
2424

src/main/scala/decaf/typecheck/Namer.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ class Namer extends Phase[Tree, Typed.Tree]("namer") with Util {
177177
if (!isStatic) formalCtx.declare(LocalVarSymbol.thisVar(ctx.currentClass.typ, id.pos))
178178
val typedParams = params.flatMap { resolveLocalVarDef(_)(formalCtx, true) }
179179
val funType = FunType(typedParams.map(_.typeLit.typ), retType)
180-
if (funType <= suspect.typ) { // override success TODO check spec
180+
if (funType <= suspect.typ) { // override success
181181
val symbol = new MethodSymbol(m, funType, formalScope, ctx.currentClass, Some(suspect))
182182
ctx.declare(symbol)
183183
val block = resolveBlock(body)(formalCtx)
@@ -223,6 +223,8 @@ class Namer extends Phase[Tree, Typed.Tree]("namer") with Util {
223223
resolved.map(_.setPos(field.pos))
224224
}
225225

226+
implicit val noReturn: Flag = No
227+
226228
def resolveBlock(block: Block)(implicit ctx: ScopeContext): Typed.Block = {
227229
val local = ctx.open(new LocalScope)
228230
val ss = block.stmts.map { resolveStmt(_)(local) }

src/main/scala/decaf/typecheck/Typer.scala

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package decaf.typecheck
33
import decaf.annot.ScopeImplicit._
44
import decaf.annot.SymbolImplicit._
55
import decaf.annot.TypeImplicit._
6+
import decaf.annot.FlagImplicit._
67
import decaf.annot._
78
import decaf.driver.{Config, Phase}
89
import decaf.error._
@@ -61,11 +62,13 @@ class Typer extends Phase[Tree, Tree]("typer") with Util {
6162
val ctx = global.open(symbol.scope)
6263
val checkedFields = fields.map {
6364
case v: VarDef => v
64-
case f @ MethodDef(id, params, returnType, body, isStatic) =>
65-
val formalCtx = ctx.open(f.symbol.scope)
65+
case m @ MethodDef(id, params, returnType, body, isStatic) =>
66+
val formalCtx = ctx.open(m.symbol.scope)
6667
val checkedBody = checkBlock(body)(formalCtx)
67-
// FIXME check every path is returned, if its return type is not void
68-
MethodDef(id, params, returnType, checkedBody, isStatic)(f.symbol).setPos(f.pos)
68+
// Check if the body always returns a value, when the method is non-void
69+
if (!m.symbol.returnType.isVoidType && checkedBody.no)
70+
issue(new MissingReturnError(checkedBody.pos))
71+
MethodDef(id, params, returnType, checkedBody, isStatic)(m.symbol).setPos(m.pos)
6972
}
7073
ClassDef(id, parent, checkedFields)(symbol).setPos(clazz.pos)
7174
}
@@ -96,10 +99,17 @@ class Typer extends Phase[Tree, Tree]("typer") with Util {
9699
}
97100
val local = ctx.open(scope)
98101
val ss = block.stmts.map { checkStmt(_)(local, insideLoop) }
99-
Block(ss).setPos(block.pos)
102+
val ret = if (ss.isEmpty) No else ss.last match {
103+
case c: ControlFlowStmt => c.flag // a block returns a value if its last statement does so
104+
case _ => No
105+
}
106+
107+
Block(ss)(ret).setPos(block.pos)
100108
}
101109

102110
def checkStmt(stmt: Stmt)(implicit ctx: ScopeContext, insideLoop: Boolean): Stmt = {
111+
implicit val noReturn: Flag = No
112+
103113
val checked = stmt match {
104114
case block: Block => checkBlock(block)
105115

@@ -126,7 +136,9 @@ class Typer extends Phase[Tree, Tree]("typer") with Util {
126136
val c = checkTestExpr(cond)
127137
val t = checkBlock(trueBranch)
128138
val f = falseBranch.map(checkBlock)
129-
If(c, t, f)
139+
// if-stmt returns a value if both branches return
140+
val ret = if (t.yes && f.isDefined && f.get.yes) Yes else No
141+
If(c, t, f)(ret)
130142

131143
case While(cond, body) =>
132144
val c = checkTestExpr(cond)
@@ -152,7 +164,7 @@ class Typer extends Phase[Tree, Tree]("typer") with Util {
152164
}
153165
val actual = e.map(_.typ).getOrElse(VoidType)
154166
if (actual.noError && !(actual <= expected)) issue(new BadReturnTypeError(expected, actual, stmt.pos))
155-
Return(e)
167+
Return(e)(Yes) // returned
156168

157169
case Print(exprs) =>
158170
val es = exprs.zipWithIndex.map {
@@ -315,7 +327,6 @@ class Typer extends Phase[Tree, Tree]("typer") with Util {
315327
if (ctx.currentMethod.isStatic) // member vars cannot be accessed in a static method
316328
issue(new RefNonStaticError(id, ctx.currentMethod.name, expr.pos))
317329
MemberVar(This(), v)(v.typ)
318-
// TODO report error: method and types are not allowed here
319330
case _ => issue(new UndeclVarError(id, expr.pos)); err
320331
}
321332
case None => issue(new UndeclVarError(id, expr.pos)); err
@@ -337,7 +348,6 @@ class Typer extends Phase[Tree, Tree]("typer") with Util {
337348
if (!(ctx.currentClass.typ <= t)) // member vars are protected
338349
issue(new FieldNotAccessError(id, t, expr.pos))
339350
MemberVar(r, v)(v.typ)
340-
// case m: MethodSymbol => TODO report error: method and types are not allowed here
341351
case _ => issue(new FieldNotFoundError(id, t, expr.pos)); err
342352
}
343353
case None => issue(new FieldNotFoundError(id, t, expr.pos)); err

0 commit comments

Comments
 (0)