> For the complete documentation index, see [llms.txt](https://mashplant.gitbook.io/decaf-doc/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://mashplant.gitbook.io/decaf-doc/pa2/yu-ju-de-fan-hui-lei-xing.md).

# 语句的返回类型

语句的返回类型的检查在类型检查中完成，因为它的规则比较复杂，这里单独列出来讲一下。这里只涉及到了框架中已经存在的部分，即判断语句的返回内容是否为空，而没有完整地计算语句的返回类型。

每个语句(`Stmt`)都有自己的返回类型，规则列举如下：

* `Assign`：返回类型为空(`Ty::void()`，下同)
* `LocalVarDef`：返回类型为空
* `ExprEval`：返回类型为空
* `Skip`：返回类型为空
* `If`：定义`on_false`分支的返回类型为：如果`on_false`分支存在，则就是它的返回类型，否则为空；`If`的返回类型不为空，当且仅当为两个分支的返回类型都不为空
* `While`：返回类型为空
* `For`：返回类型为空
* `Return`：若`return`语句无返回值则返回类型为空，否则返回类型为返回值值的类型
* `Print`：返回类型为空
* `Break`：返回类型为空
* `Block`：如果它至少包含一条语句，则它的返回类型等同于它的最后一条语句的返回类型；否则，它的返回类型为空
  * 显然这里有一些不精确的地方，例如一个`Block`中可能包含多条`return`语句，则有可能最后一条语句实际上不可达。为了简单起见，我们不考虑这种情况，测例中也不会在这里为难大家。作为一点科普，这样的不可达语句在大多数语言中都会被汇报一个警告，但在java中这是一种编译错误，可参考[section 14.21 of the JLS](https://docs.oracle.com/javase/specs/jls/se7/html/jls-14.html#jls-14.21)

如果一个函数的返回类型不为空，然而它的`body`语句块的返回类型为空，需要汇报一个对应的错误，这在基础框架中已经实现了。

大家可能会发现，对于`while`和`for`循环的检测粒度似乎太粗了一点：如果是`while (true)`或者`for (...; true; ...)`，且循环中没有`break`的情况，这个循环本身并不会结束，唯一的跳出循环的方式是通过其中的`return`语句，所以它们的返回类型也就不一定为空了。

java对这个问题的处理方式是，javac会汇报返回类型不为空的函数可能不返回值这个错误，但如果`while`或者`for`的条件是常量表达式且求值结果为`true`，并且其中没有作用于它的`break`语句，则认为它一定有返回值，不会产生编译错误。

rust对这个问题的处理方式是，rustc也强制要求保证返回类型不为空的函数一定返回值，但是它不会特殊处理`while true`，而是额外引入了`loop`关键字，运行时效果等价于`while true`，但在其中没有作用于它的`break`语句时认为它一定有返回值。

如果这样的循环中同时也没有`return`语句呢？这意味着这个循环一定是一个死循环，一旦程序执行到这个位置，则这个函数就不可能返回了，但是这并不与函数的返回类型要求不为空相矛盾。在rust中这种情况下认为函数的实际返回类型为`!`，即never type，而never type是其他所有类型的子类型，所以不会产生编译错误。java虽然语法上没有never type的概念，但是可以认为编译器内部有这个概念，类似的情况下也不会产生编译错误。这里给出两个简单的例子，它们都是合法的：

```java
// java
int foo() { while (true) {} }
```

```rust
// rust
fn foo() -> i32 { loop {} }
```

目前我们为了简单起见，这两种比较合理的方案都没有采用(第一种方案需要定义常量表达式，并且需要在类型检查阶段做常量表达式求值；第二种方案需要增加新的关键字，也许可以作为未来的实验中的某个新语法)，而是直接粗暴地认为循环语句的返回值为空。
