Skip to content

Commit 5fa7dd8

Browse files
jonmeowgeoffromer
andauthored
Implement auto return types, removing => returns (#850)
This implements #826, I think covering everything important there. Regarding ReturnTypeContext, I broke that out because it started feeling like a significant number of args to be passing around, and I think this makes the association inside type checking clearer. Co-authored-by: Geoff Romer <gromer@google.com>
1 parent 904c6f7 commit 5fa7dd8

11 files changed

+164
-46
lines changed

executable_semantics/interpreter/type_checker.cpp

+47-29
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,17 @@
2222

2323
using llvm::cast;
2424
using llvm::dyn_cast;
25+
using llvm::isa;
2526

2627
namespace Carbon {
2728

29+
TypeChecker::ReturnTypeContext::ReturnTypeContext(
30+
Nonnull<const Value*> orig_return_type, bool is_omitted)
31+
: is_auto_(isa<AutoType>(orig_return_type)),
32+
deduced_return_type_(is_auto_ ? std::nullopt
33+
: std::optional(orig_return_type)),
34+
is_omitted_(is_omitted) {}
35+
2836
void PrintTypeEnv(TypeEnv types, llvm::raw_ostream& out) {
2937
llvm::ListSeparator sep;
3038
for (const auto& [name, type] : types) {
@@ -622,18 +630,17 @@ auto TypeChecker::TypeCheckPattern(
622630
auto TypeChecker::TypeCheckCase(Nonnull<const Value*> expected,
623631
Nonnull<Pattern*> pat, Nonnull<Statement*> body,
624632
TypeEnv types, Env values,
625-
Nonnull<const Value*>& ret_type,
626-
bool is_omitted_ret_type)
633+
Nonnull<ReturnTypeContext*> return_type_context)
627634
-> std::pair<Nonnull<Pattern*>, Nonnull<Statement*>> {
628635
auto pat_res = TypeCheckPattern(pat, types, values, expected);
629-
auto res =
630-
TypeCheckStmt(body, pat_res.types, values, ret_type, is_omitted_ret_type);
636+
auto res = TypeCheckStmt(body, pat_res.types, values, return_type_context);
631637
return std::make_pair(pat, res.stmt);
632638
}
633639

634640
auto TypeChecker::TypeCheckStmt(Nonnull<Statement*> s, TypeEnv types,
635-
Env values, Nonnull<const Value*>& ret_type,
636-
bool is_omitted_ret_type) -> TCStatement {
641+
Env values,
642+
Nonnull<ReturnTypeContext*> return_type_context)
643+
-> TCStatement {
637644
switch (s->Tag()) {
638645
case Statement::Kind::Match: {
639646
auto& match = cast<Match>(*s);
@@ -644,7 +651,7 @@ auto TypeChecker::TypeCheckStmt(Nonnull<Statement*> s, TypeEnv types,
644651
for (auto& clause : match.Clauses()) {
645652
new_clauses.push_back(TypeCheckCase(res_type, clause.first,
646653
clause.second, types, values,
647-
ret_type, is_omitted_ret_type));
654+
return_type_context));
648655
}
649656
auto new_s = arena->New<Match>(s->SourceLoc(), res.exp, new_clauses);
650657
return TCStatement(new_s, types);
@@ -654,8 +661,8 @@ auto TypeChecker::TypeCheckStmt(Nonnull<Statement*> s, TypeEnv types,
654661
auto cnd_res = TypeCheckExp(while_stmt.Cond(), types, values);
655662
ExpectType(s->SourceLoc(), "condition of `while`", arena->New<BoolType>(),
656663
cnd_res.type);
657-
auto body_res = TypeCheckStmt(while_stmt.Body(), types, values, ret_type,
658-
is_omitted_ret_type);
664+
auto body_res =
665+
TypeCheckStmt(while_stmt.Body(), types, values, return_type_context);
659666
auto new_s =
660667
arena->New<While>(s->SourceLoc(), cnd_res.exp, body_res.stmt);
661668
return TCStatement(new_s, types);
@@ -666,8 +673,8 @@ auto TypeChecker::TypeCheckStmt(Nonnull<Statement*> s, TypeEnv types,
666673
case Statement::Kind::Block: {
667674
auto& block = cast<Block>(*s);
668675
if (block.Stmt()) {
669-
auto stmt_res = TypeCheckStmt(*block.Stmt(), types, values, ret_type,
670-
is_omitted_ret_type);
676+
auto stmt_res =
677+
TypeCheckStmt(*block.Stmt(), types, values, return_type_context);
671678
return TCStatement(arena->New<Block>(s->SourceLoc(), stmt_res.stmt),
672679
types);
673680
} else {
@@ -685,13 +692,13 @@ auto TypeChecker::TypeCheckStmt(Nonnull<Statement*> s, TypeEnv types,
685692
}
686693
case Statement::Kind::Sequence: {
687694
auto& seq = cast<Sequence>(*s);
688-
auto stmt_res = TypeCheckStmt(seq.Stmt(), types, values, ret_type,
689-
is_omitted_ret_type);
695+
auto stmt_res =
696+
TypeCheckStmt(seq.Stmt(), types, values, return_type_context);
690697
auto checked_types = stmt_res.types;
691698
std::optional<Nonnull<Statement*>> next_stmt;
692699
if (seq.Next()) {
693700
auto next_res = TypeCheckStmt(*seq.Next(), checked_types, values,
694-
ret_type, is_omitted_ret_type);
701+
return_type_context);
695702
next_stmt = next_res.stmt;
696703
checked_types = next_res.types;
697704
}
@@ -720,12 +727,12 @@ auto TypeChecker::TypeCheckStmt(Nonnull<Statement*> s, TypeEnv types,
720727
auto cnd_res = TypeCheckExp(if_stmt.Cond(), types, values);
721728
ExpectType(s->SourceLoc(), "condition of `if`", arena->New<BoolType>(),
722729
cnd_res.type);
723-
auto then_res = TypeCheckStmt(if_stmt.ThenStmt(), types, values, ret_type,
724-
is_omitted_ret_type);
730+
auto then_res =
731+
TypeCheckStmt(if_stmt.ThenStmt(), types, values, return_type_context);
725732
std::optional<Nonnull<Statement*>> else_stmt;
726733
if (if_stmt.ElseStmt()) {
727734
auto else_res = TypeCheckStmt(*if_stmt.ElseStmt(), types, values,
728-
ret_type, is_omitted_ret_type);
735+
return_type_context);
729736
else_stmt = else_res.stmt;
730737
}
731738
auto new_s =
@@ -735,17 +742,24 @@ auto TypeChecker::TypeCheckStmt(Nonnull<Statement*> s, TypeEnv types,
735742
case Statement::Kind::Return: {
736743
auto& ret = cast<Return>(*s);
737744
auto res = TypeCheckExp(ret.Exp(), types, values);
738-
if (ret_type->Tag() == Value::Kind::AutoType) {
739-
// The following infers the return type from the first 'return'
740-
// statement. This will get more difficult with subtyping, when we
741-
// should infer the least-upper bound of all the 'return' statements.
742-
ret_type = res.type;
745+
if (return_type_context->is_auto()) {
746+
if (return_type_context->deduced_return_type()) {
747+
// Only one return is allowed when the return type is `auto`.
748+
FATAL_COMPILATION_ERROR(s->SourceLoc())
749+
<< "Only one return is allowed in a function with an `auto` "
750+
"return type.";
751+
} else {
752+
// Infer the auto return from the first `return` statement.
753+
return_type_context->set_deduced_return_type(res.type);
754+
}
743755
} else {
744-
ExpectType(s->SourceLoc(), "return", ret_type, res.type);
756+
ExpectType(s->SourceLoc(), "return",
757+
*return_type_context->deduced_return_type(), res.type);
745758
}
746-
if (ret.IsOmittedExp() != is_omitted_ret_type) {
759+
if (ret.IsOmittedExp() != return_type_context->is_omitted()) {
747760
FATAL_COMPILATION_ERROR(s->SourceLoc())
748-
<< *s << " should" << (is_omitted_ret_type ? " not" : "")
761+
<< *s << " should"
762+
<< (return_type_context->is_omitted() ? " not" : "")
749763
<< " provide a return value, to match the function's signature.";
750764
}
751765
return TCStatement(
@@ -754,8 +768,8 @@ auto TypeChecker::TypeCheckStmt(Nonnull<Statement*> s, TypeEnv types,
754768
}
755769
case Statement::Kind::Continuation: {
756770
auto& cont = cast<Continuation>(*s);
757-
TCStatement body_result = TypeCheckStmt(cont.Body(), types, values,
758-
ret_type, is_omitted_ret_type);
771+
TCStatement body_result =
772+
TypeCheckStmt(cont.Body(), types, values, return_type_context);
759773
auto new_continuation = arena->New<Continuation>(
760774
s->SourceLoc(), cont.ContinuationVariable(), body_result.stmt);
761775
types.Set(cont.ContinuationVariable(), arena->New<ContinuationType>());
@@ -875,9 +889,13 @@ auto TypeChecker::TypeCheckFunDef(FunctionDefinition* f, TypeEnv types,
875889
}
876890
std::optional<Nonnull<Statement*>> body_stmt;
877891
if (f->body()) {
878-
auto res = TypeCheckStmt(*f->body(), param_res.types, values, return_type,
879-
f->is_omitted_return_type());
892+
ReturnTypeContext return_type_context(return_type,
893+
f->is_omitted_return_type());
894+
auto res = TypeCheckStmt(*f->body(), param_res.types, values,
895+
&return_type_context);
880896
body_stmt = res.stmt;
897+
// Save the return type in case it changed.
898+
return_type = *return_type_context.deduced_return_type();
881899
}
882900
auto body = CheckOrEnsureReturn(body_stmt, f->is_omitted_return_type(),
883901
f->source_loc());

executable_semantics/interpreter/type_checker.h

+33-2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,37 @@ class TypeChecker {
3838
auto TopLevel(std::vector<Nonnull<Declaration*>>* fs) -> TypeCheckContext;
3939

4040
private:
41+
// Context about the return type, which may be updated during type checking.
42+
class ReturnTypeContext {
43+
public:
44+
// If orig_return_type is auto, deduced_return_type_ will be nullopt;
45+
// otherwise, it's orig_return_type. is_auto_ is set accordingly.
46+
ReturnTypeContext(Nonnull<const Value*> orig_return_type, bool is_omitted);
47+
48+
auto is_auto() const -> bool { return is_auto_; }
49+
50+
auto deduced_return_type() const -> std::optional<Nonnull<const Value*>> {
51+
return deduced_return_type_;
52+
}
53+
void set_deduced_return_type(Nonnull<const Value*> type) {
54+
deduced_return_type_ = type;
55+
}
56+
57+
auto is_omitted() const -> bool { return is_omitted_; }
58+
59+
private:
60+
// Indicates an `auto` return type, as in `fn Foo() -> auto { return 0; }`.
61+
const bool is_auto_;
62+
63+
// The actual return type. May be nullopt for an `auto` return type that has
64+
// yet to be determined.
65+
std::optional<Nonnull<const Value*>> deduced_return_type_;
66+
67+
// Indicates the return type was omitted and is implicitly the empty tuple,
68+
// as in `fn Foo() {}`.
69+
const bool is_omitted_;
70+
};
71+
4172
struct TCExpression {
4273
TCExpression(Nonnull<Expression*> e, Nonnull<const Value*> t, TypeEnv types)
4374
: exp(e), type(t), types(types) {}
@@ -90,15 +121,15 @@ class TypeChecker {
90121
// type is "auto", then the return type is inferred from the first return
91122
// statement.
92123
auto TypeCheckStmt(Nonnull<Statement*> s, TypeEnv types, Env values,
93-
Nonnull<const Value*>& ret_type, bool is_omitted_ret_type)
124+
Nonnull<ReturnTypeContext*> return_type_context)
94125
-> TCStatement;
95126

96127
auto TypeCheckFunDef(FunctionDefinition* f, TypeEnv types, Env values)
97128
-> Nonnull<FunctionDefinition*>;
98129

99130
auto TypeCheckCase(Nonnull<const Value*> expected, Nonnull<Pattern*> pat,
100131
Nonnull<Statement*> body, TypeEnv types, Env values,
101-
Nonnull<const Value*>& ret_type, bool is_omitted_ret_type)
132+
Nonnull<ReturnTypeContext*> return_type_context)
102133
-> std::pair<Nonnull<Pattern*>, Nonnull<Statement*>>;
103134

104135
auto TypeOfFunDef(TypeEnv types, Env values, FunctionDefinition* fun_def)

executable_semantics/syntax/parser.ypp

+4-6
Original file line numberDiff line numberDiff line change
@@ -617,15 +617,13 @@ function_definition:
617617
context.SourceLoc(), $2, $3, $4,
618618
arena->New<ExpressionPattern>(return_exp), is_omitted_exp, $6);
619619
}
620-
| FN identifier deduced_params maybe_empty_tuple_pattern DOUBLE_ARROW expression
621-
SEMICOLON
620+
| FN identifier deduced_params maybe_empty_tuple_pattern ARROW AUTO block
622621
{
623-
// The return type is not considered "omitted" because it's automatic from
624-
// the expression.
622+
// The return type is not considered "omitted" because it's `auto`.
625623
$$ = arena->New<FunctionDefinition>(
626624
context.SourceLoc(), $2, $3, $4,
627-
arena->New<AutoPattern>(context.SourceLoc()), true,
628-
arena->New<Return>(context.SourceLoc(), $6, true));
625+
arena->New<AutoPattern>(context.SourceLoc()),
626+
/*is_omitted_exp=*/false, $7);
629627
}
630628
;
631629
function_declaration:

executable_semantics/testdata/function/auto_return_add.carbon executable_semantics/testdata/function/auto_return/add.carbon

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
package ExecutableSemanticsTest api;
1313

14-
fn add(x: i32, y: i32) => x + y;
14+
fn add(x: i32, y: i32) -> auto { return x + y; }
1515

1616
fn main() -> i32 {
1717
return add(1, 2) - 3;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
2+
// Exceptions. See /LICENSE for license information.
3+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4+
//
5+
// RUN: not executable_semantics %s 2>&1 | \
6+
// RUN: FileCheck --match-full-lines --allow-unused-prefixes=false %s
7+
// RUN: not executable_semantics --trace %s 2>&1 | \
8+
// RUN: FileCheck --match-full-lines --allow-unused-prefixes %s
9+
// AUTOUPDATE: executable_semantics %s
10+
// CHECK: COMPILATION ERROR: {{.*}}/executable_semantics/testdata/function/auto_return/fail_direct_recurse.carbon:18: could not find `Recurse`
11+
12+
package ExecutableSemanticsTest api;
13+
14+
// This is required to fail even though the Recurse() call's return value isn't
15+
// used.
16+
fn Recurse(x: i32, do_recurse: Bool) -> auto {
17+
if (do_recurse) {
18+
Recurse(x, false);
19+
}
20+
return x;
21+
}
22+
23+
fn main() -> i32 {
24+
return Recurse(1, true) - 3;
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
2+
// Exceptions. See /LICENSE for license information.
3+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4+
//
5+
// RUN: not executable_semantics %s 2>&1 | \
6+
// RUN: FileCheck --match-full-lines --allow-unused-prefixes=false %s
7+
// RUN: not executable_semantics --trace %s 2>&1 | \
8+
// RUN: FileCheck --match-full-lines --allow-unused-prefixes %s
9+
// AUTOUPDATE: executable_semantics %s
10+
// CHECK: COMPILATION ERROR: {{.*}}/executable_semantics/testdata/function/auto_return/fail_multiple_returns.carbon:18: Only one return is allowed in a function with an `auto` return type.
11+
12+
package ExecutableSemanticsTest api;
13+
14+
fn Add(x: i32, y: i32) -> auto {
15+
if (x == 0) {
16+
return x;
17+
} else if (y == 0) {
18+
return y;
19+
} else {
20+
return x + y;
21+
}
22+
}
23+
24+
fn main() -> i32 {
25+
return Add(1, 2) - 3;
26+
}

executable_semantics/testdata/return/fail_auto.carbon executable_semantics/testdata/function/auto_return/fail_no_return.carbon

+4-5
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,13 @@
77
// RUN: not executable_semantics --trace %s 2>&1 | \
88
// RUN: FileCheck --match-full-lines --allow-unused-prefixes %s
99
// AUTOUPDATE: executable_semantics %s
10-
// CHECK: COMPILATION ERROR: {{.*}}/executable_semantics/testdata/return/fail_auto.carbon:14: syntax error, unexpected AUTO
10+
// CHECK: COMPILATION ERROR: {{.*}}/executable_semantics/testdata/function/auto_return/fail_no_return.carbon:15: control-flow reaches end of function that provides a `->` return type without reaching a return statement
1111

1212
package ExecutableSemanticsTest api;
1313

14-
fn F() -> auto {
15-
return 0;
14+
fn NoReturn() -> auto {
1615
}
1716

18-
fn main() -> i32 {
19-
return F();
17+
fn main() {
18+
NoReturn();
2019
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
2+
// Exceptions. See /LICENSE for license information.
3+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4+
//
5+
// RUN: not executable_semantics %s 2>&1 | \
6+
// RUN: FileCheck --match-full-lines --allow-unused-prefixes=false %s
7+
// RUN: not executable_semantics --trace %s 2>&1 | \
8+
// RUN: FileCheck --match-full-lines --allow-unused-prefixes %s
9+
// AUTOUPDATE: executable_semantics %s
10+
// CHECK: COMPILATION ERROR: {{.*}}/executable_semantics/testdata/function/auto_return/fail_separate_decl.carbon:15: syntax error, unexpected SEMICOLON, expecting LEFT_CURLY_BRACE
11+
12+
package ExecutableSemanticsTest api;
13+
14+
// This declaration is not allowed.
15+
fn Add(x: i32, y: i32) -> auto;
16+
17+
fn Add(x: i32, y: i32) -> auto { return x + y; }
18+
19+
fn main() -> i32 {
20+
return Add(1, 2) - 3;
21+
}

executable_semantics/testdata/function/auto_return_modify_arg_type.carbon executable_semantics/testdata/function/auto_return/modify_arg_type.carbon

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
package ExecutableSemanticsTest api;
1313

14-
fn Id(t: Type) => t;
14+
fn Id(t: Type) -> auto { return t; }
1515

1616
// Test non-trivial type expression in parameter type.
1717

executable_semantics/testdata/function/auto_return_modify_return_type.carbon executable_semantics/testdata/function/auto_return/modify_return_type.carbon

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
package ExecutableSemanticsTest api;
1313

14-
fn Id(t: Type) => t;
14+
fn Id(t: Type) -> auto { return t; }
1515

1616
// Test non-trivial type expression in return type.
1717

executable_semantics/testdata/function/auto_return_type.carbon executable_semantics/testdata/function/auto_return/type.carbon

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
package ExecutableSemanticsTest api;
1313

14-
fn Id(t: Type) => t;
14+
fn Id(t: Type) -> auto { return t; }
1515

1616
// Test non-trivial type expression in variable declaration statement.
1717

0 commit comments

Comments
 (0)