语句的返回类型

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

每个语句(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

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

大家可能会发现,对于whilefor循环的检测粒度似乎太粗了一点:如果是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
int foo() { while (true) {} }
// rust
fn foo() -> i32 { loop {} }

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

Last updated