Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Let variables #1095

Merged
merged 13 commits into from
Mar 11, 2022
Merged
8 changes: 6 additions & 2 deletions executable_semantics/ast/declaration.h
Original file line number Diff line number Diff line change
Expand Up @@ -300,10 +300,12 @@ class VariableDeclaration : public Declaration {
public:
VariableDeclaration(SourceLocation source_loc,
Nonnull<BindingPattern*> binding,
std::optional<Nonnull<Expression*>> initializer)
std::optional<Nonnull<Expression*>> initializer,
ValueCategory value_category)
: Declaration(AstNodeKind::VariableDeclaration, source_loc),
binding_(binding),
initializer_(initializer) {}
initializer_(initializer),
value_category_(value_category) {}

static auto classof(const AstNode* node) -> bool {
return InheritsFromVariableDeclaration(node->kind());
Expand All @@ -313,6 +315,7 @@ class VariableDeclaration : public Declaration {
auto binding() -> BindingPattern& { return *binding_; }
auto initializer() const -> const Expression& { return **initializer_; }
auto initializer() -> Expression& { return **initializer_; }
auto value_category() const -> ValueCategory { return value_category_; }

bool has_initializer() const { return initializer_.has_value(); }

Expand All @@ -322,6 +325,7 @@ class VariableDeclaration : public Declaration {
// missing name.
Nonnull<BindingPattern*> binding_;
std::optional<Nonnull<Expression*>> initializer_;
ValueCategory value_category_;
};

} // namespace Carbon
Expand Down
28 changes: 25 additions & 3 deletions executable_semantics/ast/pattern.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,12 @@ class BindingPattern : public Pattern {
using ImplementsCarbonNamedEntity = void;

BindingPattern(SourceLocation source_loc, std::string name,
Nonnull<Pattern*> type)
Nonnull<Pattern*> type,
std::optional<ValueCategory> value_category)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking observation: I'd prefer if we could always get this from the typechecker, rather than sometimes as a constructor parameter, but I think this is a consequence of some pre-existing problems that are out of scope for this PR.

: Pattern(AstNodeKind::BindingPattern, source_loc),
name_(std::move(name)),
type_(type) {}
type_(type),
value_category_(value_category) {}

static auto classof(const AstNode* node) -> bool {
return InheritsFromBindingPattern(node->kind());
Expand All @@ -122,7 +124,26 @@ class BindingPattern : public Pattern {
auto type() const -> const Pattern& { return *type_; }
auto type() -> Pattern& { return *type_; }

auto value_category() const -> ValueCategory { return ValueCategory::Var; }
// Gets the value category of this pattern. Can only be called after
DarshalShetty marked this conversation as resolved.
Show resolved Hide resolved
// typechecking.
auto value_category() const -> ValueCategory {
return value_category_.value();
}

// Returns whether the value category has been set. Should only be called
// during typechecking: before typechecking it's false if "var" or "let"
// hasn't been explicitly specified for this pattern, and after typechecking
// it's guaranteed to be true.
auto has_value_category() const -> bool {
DarshalShetty marked this conversation as resolved.
Show resolved Hide resolved
return value_category_.has_value();
}

// Sets the value category of the variable being bound. Can only be called
// once during typechecking
void set_value_category(ValueCategory vc) {
CHECK(!value_category_.has_value());
value_category_ = vc;
}

auto constant_value() const -> std::optional<Nonnull<const Value*>> {
return std::nullopt;
Expand All @@ -131,6 +152,7 @@ class BindingPattern : public Pattern {
private:
std::string name_;
Nonnull<Pattern*> type_;
std::optional<ValueCategory> value_category_;
};

// A pattern that matches a tuple value field-wise.
Expand Down
7 changes: 5 additions & 2 deletions executable_semantics/ast/statement.h
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,11 @@ class Assign : public Statement {
class VariableDefinition : public Statement {
public:
VariableDefinition(SourceLocation source_loc, Nonnull<Pattern*> pattern,
Nonnull<Expression*> init)
Nonnull<Expression*> init, ValueCategory value_category)
: Statement(AstNodeKind::VariableDefinition, source_loc),
pattern_(pattern),
init_(init) {}
init_(init),
value_category_(value_category) {}

static auto classof(const AstNode* node) -> bool {
return InheritsFromVariableDefinition(node->kind());
Expand All @@ -121,10 +122,12 @@ class VariableDefinition : public Statement {
auto pattern() -> Pattern& { return *pattern_; }
auto init() const -> const Expression& { return *init_; }
auto init() -> Expression& { return *init_; }
auto value_category() const -> ValueCategory { return value_category_; }

private:
Nonnull<Pattern*> pattern_;
Nonnull<Expression*> init_;
ValueCategory value_category_;
};

class If : public Statement {
Expand Down
25 changes: 16 additions & 9 deletions executable_semantics/interpreter/type_checker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -753,7 +753,8 @@ void TypeChecker::TypeCheckExp(Nonnull<Expression*> e) {
}

void TypeChecker::TypeCheckPattern(
Nonnull<Pattern*> p, std::optional<Nonnull<const Value*>> expected) {
Nonnull<Pattern*> p, std::optional<Nonnull<const Value*>> expected,
ValueCategory enclosing_value_category) {
if (trace_) {
llvm::outs() << "checking pattern " << *p;
if (expected) {
Expand All @@ -770,7 +771,7 @@ void TypeChecker::TypeCheckPattern(
}
case PatternKind::BindingPattern: {
auto& binding = cast<BindingPattern>(*p);
TypeCheckPattern(&binding.type(), std::nullopt);
TypeCheckPattern(&binding.type(), std::nullopt, enclosing_value_category);
Nonnull<const Value*> type =
InterpPattern(&binding.type(), arena_, trace_);
if (expected) {
Expand All @@ -789,6 +790,10 @@ void TypeChecker::TypeCheckPattern(
ExpectIsConcreteType(binding.source_loc(), type);
SetStaticType(&binding, type);
SetValue(&binding, InterpPattern(&binding, arena_, trace_));

if (!binding.has_value_category()) {
binding.set_value_category(enclosing_value_category);
}
return;
}
case PatternKind::TuplePattern: {
Expand All @@ -808,7 +813,7 @@ void TypeChecker::TypeCheckPattern(
if (expected) {
expected_field_type = cast<TupleValue>(**expected).elements()[i];
}
TypeCheckPattern(field, expected_field_type);
TypeCheckPattern(field, expected_field_type, enclosing_value_category);
field_types.push_back(&field->static_type());
}
SetStaticType(&tuple, arena_->New<TupleValue>(std::move(field_types)));
Expand Down Expand Up @@ -838,7 +843,8 @@ void TypeChecker::TypeCheckPattern(
<< "'" << alternative.alternative_name()
<< "' is not an alternative of " << choice_type;
}
TypeCheckPattern(&alternative.arguments(), *parameter_types);
TypeCheckPattern(&alternative.arguments(), *parameter_types,
enclosing_value_category);
SetStaticType(&alternative, &choice_type);
SetValue(&alternative, InterpPattern(&alternative, arena_, trace_));
return;
Expand All @@ -860,7 +866,8 @@ void TypeChecker::TypeCheckStmt(Nonnull<Statement*> s) {
TypeCheckExp(&match.expression());
std::vector<Match::Clause> new_clauses;
for (auto& clause : match.clauses()) {
TypeCheckPattern(&clause.pattern(), &match.expression().static_type());
TypeCheckPattern(&clause.pattern(), &match.expression().static_type(),
ValueCategory::Let);
TypeCheckStmt(&clause.statement());
}
return;
Expand Down Expand Up @@ -888,7 +895,7 @@ void TypeChecker::TypeCheckStmt(Nonnull<Statement*> s) {
auto& var = cast<VariableDefinition>(*s);
TypeCheckExp(&var.init());
const Value& rhs_ty = var.init().static_type();
TypeCheckPattern(&var.pattern(), &rhs_ty);
TypeCheckPattern(&var.pattern(), &rhs_ty, var.value_category());
return;
}
case StatementKind::Assign: {
Expand Down Expand Up @@ -1036,11 +1043,11 @@ void TypeChecker::TypeCheckFunctionDeclaration(Nonnull<FunctionDeclaration*> f,
}
if (f->is_method()) {
// Type check the receiver patter
TypeCheckPattern(&f->me_pattern(), std::nullopt);
TypeCheckPattern(&f->me_pattern(), std::nullopt, ValueCategory::Let);
}

// Type check the parameter pattern
TypeCheckPattern(&f->param_pattern(), std::nullopt);
TypeCheckPattern(&f->param_pattern(), std::nullopt, ValueCategory::Let);

// Evaluate the return type, if we can do so without examining the body.
if (std::optional<Nonnull<Expression*>> return_expression =
Expand Down Expand Up @@ -1198,7 +1205,7 @@ void TypeChecker::DeclareDeclaration(Nonnull<Declaration*> d) {
// compile-time symbol table.
Expression& type =
cast<ExpressionPattern>(var.binding().type()).expression();
TypeCheckPattern(&var.binding(), std::nullopt);
TypeCheckPattern(&var.binding(), std::nullopt, var.value_category());
Nonnull<const Value*> declared_type = InterpExp(&type, arena_, trace_);
SetStaticType(&var, declared_type);
break;
Expand Down
3 changes: 2 additions & 1 deletion executable_semantics/interpreter/type_checker.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ class TypeChecker {
// surrounding context gives us that information. Otherwise, it is
// nullopt.
void TypeCheckPattern(Nonnull<Pattern*> p,
std::optional<Nonnull<const Value*>> expected);
std::optional<Nonnull<const Value*>> expected,
ValueCategory enclosing_value_category);

// Equivalent to TypeCheckExp, but operates on the AST rooted at `d`.
void TypeCheckDeclaration(Nonnull<Declaration*> d);
Expand Down
2 changes: 2 additions & 0 deletions executable_semantics/syntax/lexer.lpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ IMPORT "import"
LEFT_CURLY_BRACE "{"
LEFT_PARENTHESIS "("
LEFT_SQUARE_BRACKET "["
LET "let"
LIBRARY "library"
MATCH "match"
MINUS "-"
Expand Down Expand Up @@ -166,6 +167,7 @@ string_literal \"([^\\\"\n\v\f\r]|\\.)*\"
{LEFT_CURLY_BRACE} { return SIMPLE_TOKEN(LEFT_CURLY_BRACE); }
{LEFT_PARENTHESIS} { return SIMPLE_TOKEN(LEFT_PARENTHESIS); }
{LEFT_SQUARE_BRACKET} { return SIMPLE_TOKEN(LEFT_SQUARE_BRACKET); }
{LET} { return SIMPLE_TOKEN(LET); }
{LIBRARY} { return SIMPLE_TOKEN(LIBRARY); }
{MATCH} { return SIMPLE_TOKEN(MATCH); }
{MINUS} { return SIMPLE_TOKEN(MINUS); }
Expand Down
47 changes: 41 additions & 6 deletions executable_semantics/syntax/parser.ypp
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
#include "executable_semantics/ast/expression.h"
#include "executable_semantics/ast/paren_contents.h"
#include "executable_semantics/ast/pattern.h"
#include "executable_semantics/ast/value_category.h"
#include "executable_semantics/common/arena.h"
#include "executable_semantics/common/nonnull.h"
#include "executable_semantics/syntax/bison_wrap.h"
Expand Down Expand Up @@ -171,6 +172,7 @@
LEFT_CURLY_BRACE
LEFT_PARENTHESIS
LEFT_SQUARE_BRACKET
LET
LIBRARY
MATCH
MINUS
Expand Down Expand Up @@ -474,7 +476,20 @@ non_expression_pattern:
AUTO
{ $$ = arena->New<AutoPattern>(context.source_loc()); }
| binding_lhs COLON pattern
{ $$ = arena->New<BindingPattern>(context.source_loc(), $1, $3); }
{
$$ = arena->New<BindingPattern>(context.source_loc(), $1, $3,
std::nullopt);
}
| LET binding_lhs COLON pattern
{
$$ = arena->New<BindingPattern>(context.source_loc(), $2, $4,
ValueCategory::Let);
}
| VAR binding_lhs COLON pattern
{
$$ = arena->New<BindingPattern>(context.source_loc(), $2, $4,
ValueCategory::Var);
}
| paren_pattern
{ $$ = $1; }
| expression tuple_pattern
Expand Down Expand Up @@ -543,7 +558,8 @@ clause:
{
$$ = Match::Clause(arena->New<BindingPattern>(
context.source_loc(), std::string(AnonymousName),
arena->New<AutoPattern>(context.source_loc())),
arena->New<AutoPattern>(context.source_loc()),
ValueCategory::Let),
$3);
}
;
Expand All @@ -560,7 +576,15 @@ statement:
expression EQUAL expression SEMICOLON
{ $$ = arena->New<Assign>(context.source_loc(), $1, $3); }
| VAR pattern EQUAL expression SEMICOLON
{ $$ = arena->New<VariableDefinition>(context.source_loc(), $2, $4); }
{
$$ = arena->New<VariableDefinition>(context.source_loc(), $2, $4,
ValueCategory::Var);
}
| LET pattern EQUAL expression SEMICOLON
{
$$ = arena->New<VariableDefinition>(context.source_loc(), $2, $4,
ValueCategory::Let);
}
| expression SEMICOLON
{ $$ = arena->New<ExpressionStatement>(context.source_loc(), $1); }
| if_statement
Expand Down Expand Up @@ -699,7 +723,10 @@ function_declaration:
}
;
variable_declaration: identifier COLON pattern
{ $$ = arena->New<BindingPattern>(context.source_loc(), $1, $3); }
{
$$ = arena->New<BindingPattern>(context.source_loc(), $1, $3,
std::nullopt);
}
;
alternative:
identifier tuple
Expand Down Expand Up @@ -738,10 +765,18 @@ declaration:
| VAR variable_declaration SEMICOLON
{
$$ = arena->New<VariableDeclaration>(context.source_loc(), $2,
std::nullopt);
std::nullopt, ValueCategory::Var);
}
| VAR variable_declaration EQUAL expression SEMICOLON
{ $$ = arena->New<VariableDeclaration>(context.source_loc(), $2, $4); }
{
$$ = arena->New<VariableDeclaration>(context.source_loc(), $2, $4,
ValueCategory::Var);
}
| LET variable_declaration EQUAL expression SEMICOLON
{
$$ = arena->New<VariableDeclaration>(context.source_loc(), $2, $4,
ValueCategory::Let);
}
;
declaration_list:
// Empty
Expand Down
23 changes: 23 additions & 0 deletions executable_semantics/testdata/let/fail_function_args.carbon
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
// RUN: %{not} %{executable_semantics} %s 2>&1 | \
// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
// RUN: %{not} %{executable_semantics} --trace %s 2>&1 | \
// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s
// AUTOUPDATE: %{executable_semantics} %s
// CHECK: COMPILATION ERROR: {{.*}}/executable_semantics/testdata/let/fail_function_args.carbon:16: Cannot assign to rvalue 'y'

package ExecutableSemanticsTest api;

fn f((var x: i32, y: i32)) -> i32 {
x = 0;
y = 0;
return x - 1;
}

fn Main() -> i32 {
var t: auto = (1, 2);
return f(t);
}
19 changes: 19 additions & 0 deletions executable_semantics/testdata/let/fail_global_assign.carbon
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
// RUN: %{not} %{executable_semantics} %s 2>&1 | \
// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
// RUN: %{not} %{executable_semantics} --trace %s 2>&1 | \
// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s
// AUTOUPDATE: %{executable_semantics} %s
// CHECK: COMPILATION ERROR: {{.*}}/executable_semantics/testdata/let/fail_global_assign.carbon:17: Cannot assign to rvalue 'x'

package ExecutableSemanticsTest api;

let x: i32 = 10;

fn Main() -> i32 {
x = 0;
return 0;
}
18 changes: 18 additions & 0 deletions executable_semantics/testdata/let/fail_local_assign.carbon
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
// RUN: %{not} %{executable_semantics} %s 2>&1 | \
// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
// RUN: %{not} %{executable_semantics} --trace %s 2>&1 | \
// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s
// AUTOUPDATE: %{executable_semantics} %s
// CHECK: COMPILATION ERROR: {{.*}}/executable_semantics/testdata/let/fail_local_assign.carbon:16: Cannot assign to rvalue 'x'

package ExecutableSemanticsTest api;

fn Main() -> i32 {
let x: auto = 10;
x = 0;
return 0;
}
Loading