diff --git a/executable_semantics/ast/BUILD b/executable_semantics/ast/BUILD index ee9ebafd27ae..fc99bc251999 100644 --- a/executable_semantics/ast/BUILD +++ b/executable_semantics/ast/BUILD @@ -16,6 +16,7 @@ cc_library( deps = [ ":function_definition", ":member", + ":pattern", ":struct_definition", "//common:ostream", "//executable_semantics/interpreter:address", @@ -33,10 +34,26 @@ cc_library( "//common:ostream", "//executable_semantics/common:arena", "//executable_semantics/common:error", + "//executable_semantics/syntax:paren_contents", "@llvm-project//llvm:Support", ], ) +cc_test( + name = "expression_test", + srcs = ["expression_test.cpp"], + env = { + # TODO(#580): Remove this when leaks are fixed. + "ASAN_OPTIONS": "detect_leaks=0", + }, + deps = [ + ":expression", + "//executable_semantics/syntax:paren_contents", + "@llvm-project//llvm:gtest", + "@llvm-project//llvm:gtest_main", + ], +) + cc_library( name = "function_definition", srcs = ["function_definition.cpp"], @@ -52,19 +69,47 @@ cc_library( name = "member", srcs = ["member.cpp"], hdrs = ["member.h"], + deps = [ + ":pattern", + "//common:ostream", + ], +) + +cc_library( + name = "pattern", + srcs = ["pattern.cpp"], + hdrs = ["pattern.h"], deps = [ ":expression", "//common:ostream", + "//executable_semantics/common:error", "@llvm-project//llvm:Support", ], ) +cc_test( + name = "pattern_test", + srcs = ["pattern_test.cpp"], + env = { + # TODO(#580): Remove this when leaks are fixed. + "ASAN_OPTIONS": "detect_leaks=0", + }, + deps = [ + ":pattern", + "//executable_semantics/syntax:paren_contents", + "@llvm-project//llvm:Support", + "@llvm-project//llvm:gtest", + "@llvm-project//llvm:gtest_main", + ], +) + cc_library( name = "statement", srcs = ["statement.cpp"], hdrs = ["statement.h"], deps = [ ":expression", + ":pattern", "//common:check", "//common:ostream", "@llvm-project//llvm:Support", diff --git a/executable_semantics/ast/declaration.cpp b/executable_semantics/ast/declaration.cpp index f679f18d8682..0f84cf1845fe 100644 --- a/executable_semantics/ast/declaration.cpp +++ b/executable_semantics/ast/declaration.cpp @@ -35,14 +35,13 @@ auto Declaration::MakeChoiceDeclaration( return d; } -auto Declaration::MakeVariableDeclaration(int source_location, std::string name, - const Expression* type, +auto Declaration::MakeVariableDeclaration(int source_location, + const BindingPattern* binding, const Expression* initializer) -> const Declaration { Declaration d; d.value = VariableDeclaration({.source_location = source_location, - .name = std::move(name), - .type = type, + .binding = binding, .initializer = initializer}); return d; } @@ -91,8 +90,7 @@ void Declaration::Print(llvm::raw_ostream& out) const { case DeclarationKind::VariableDeclaration: { const auto& var = GetVariableDeclaration(); - out << "var " << *var.type << " : " << var.name << " = " - << *var.initializer << "\n"; + out << "var " << *var.binding << " = " << *var.initializer << "\n"; break; } } diff --git a/executable_semantics/ast/declaration.h b/executable_semantics/ast/declaration.h index f0cd0fffceb0..8aeeaa156fae 100644 --- a/executable_semantics/ast/declaration.h +++ b/executable_semantics/ast/declaration.h @@ -11,6 +11,7 @@ #include "common/ostream.h" #include "executable_semantics/ast/function_definition.h" #include "executable_semantics/ast/member.h" +#include "executable_semantics/ast/pattern.h" #include "executable_semantics/ast/struct_definition.h" #include "executable_semantics/interpreter/address.h" #include "executable_semantics/interpreter/dictionary.h" @@ -58,8 +59,10 @@ struct ChoiceDeclaration { struct VariableDeclaration { static constexpr DeclarationKind Kind = DeclarationKind::VariableDeclaration; int source_location; - std::string name; - const Expression* type; + // TODO: split this into a non-optional name and a type, initialized by + // a constructor that takes a BindingPattern and handles errors like a + // missing name. + const BindingPattern* binding; const Expression* initializer; }; @@ -74,8 +77,8 @@ class Declaration { int line_num, std::string name, std::list> alternatives) -> const Declaration; - static auto MakeVariableDeclaration(int source_location, std::string name, - const Expression* type, + static auto MakeVariableDeclaration(int source_location, + const BindingPattern* binding, const Expression* initializer) -> const Declaration; diff --git a/executable_semantics/ast/expression.cpp b/executable_semantics/ast/expression.cpp index b67ddaa06ea8..c7c72a190a69 100644 --- a/executable_semantics/ast/expression.cpp +++ b/executable_semantics/ast/expression.cpp @@ -4,6 +4,8 @@ #include "executable_semantics/ast/expression.h" +#include + #include "executable_semantics/common/arena.h" #include "executable_semantics/common/error.h" #include "llvm/ADT/StringExtras.h" @@ -11,6 +13,24 @@ namespace Carbon { +auto ExpressionFromParenContents( + int line_num, const ParenContents& paren_contents) + -> const Expression* { + std::optional single_term = paren_contents.SingleTerm(); + if (single_term.has_value()) { + return *single_term; + } else { + return TupleExpressionFromParenContents(line_num, paren_contents); + } +} + +auto TupleExpressionFromParenContents( + int line_num, const ParenContents& paren_contents) + -> const Expression* { + return Expression::MakeTupleLiteral( + line_num, paren_contents.TupleElements(line_num)); +} + auto Expression::GetIdentifierExpression() const -> const IdentifierExpression& { return std::get(value); @@ -25,10 +45,6 @@ auto Expression::GetIndexExpression() const -> const IndexExpression& { return std::get(value); } -auto Expression::GetBindingExpression() const -> const BindingExpression& { - return std::get(value); -} - auto Expression::GetIntLiteral() const -> int { return std::get(value).value; } @@ -75,13 +91,6 @@ auto Expression::MakeBoolTypeLiteral(int line_num) -> const Expression* { return t; } -auto Expression::MakeAutoTypeLiteral(int line_num) -> const Expression* { - auto* t = global_arena->New(); - t->line_num = line_num; - t->value = AutoTypeLiteral(); - return t; -} - // Returns a Continuation type AST node at the given source location. auto Expression::MakeContinuationTypeLiteral(int line_num) -> const Expression* { @@ -108,16 +117,6 @@ auto Expression::MakeIdentifierExpression(int line_num, std::string var) return v; } -auto Expression::MakeBindingExpression(int line_num, - std::optional var, - const Expression* type) - -> const Expression* { - auto* v = global_arena->New(); - v->line_num = line_num; - v->value = BindingExpression({.name = std::move(var), .type = type}); - return v; -} - auto Expression::MakeIntLiteral(int line_num, int i) -> const Expression* { auto* e = global_arena->New(); e->line_num = line_num; @@ -166,21 +165,7 @@ auto Expression::MakeTupleLiteral(int line_num, -> const Expression* { auto* e = global_arena->New(); e->line_num = line_num; - int i = 0; - bool seen_named_member = false; - for (auto& arg : args) { - if (arg.name == "") { - if (seen_named_member) { - FATAL_USER_ERROR(line_num) - << "positional members must come before named members"; - } - arg.name = std::to_string(i); - ++i; - } else { - seen_named_member = true; - } - } - e->value = TupleLiteral({.fields = args}); + e->value = TupleLiteral({.fields = std::move(args)}); return e; } @@ -269,16 +254,6 @@ void Expression::Print(llvm::raw_ostream& out) const { case ExpressionKind::IdentifierExpression: out << GetIdentifierExpression().name; break; - case ExpressionKind::BindingExpression: { - const BindingExpression& binding = GetBindingExpression(); - if (binding.name.has_value()) { - out << *binding.name; - } else { - out << "_"; - } - out << ": " << *binding.type; - break; - } case ExpressionKind::CallExpression: out << *GetCallExpression().function; if (GetCallExpression().argument->tag() == ExpressionKind::TupleLiteral) { @@ -296,9 +271,6 @@ void Expression::Print(llvm::raw_ostream& out) const { case ExpressionKind::TypeTypeLiteral: out << "Type"; break; - case ExpressionKind::AutoTypeLiteral: - out << "auto"; - break; case ExpressionKind::ContinuationTypeLiteral: out << "Continuation"; break; diff --git a/executable_semantics/ast/expression.h b/executable_semantics/ast/expression.h index 0790c27d5501..c5cd788ee352 100644 --- a/executable_semantics/ast/expression.h +++ b/executable_semantics/ast/expression.h @@ -11,15 +11,33 @@ #include #include "common/ostream.h" +#include "executable_semantics/syntax/paren_contents.h" #include "llvm/Support/Compiler.h" namespace Carbon { struct Expression; +class Pattern; + +// Converts paren_contents to an Expression, interpreting the parentheses as +// grouping if their contents permit that interpretation, or as forming a +// tuple otherwise. +auto ExpressionFromParenContents( + int line_num, const ParenContents& paren_contents) + -> const Expression*; + +// Converts paren_contents to an Expression, interpreting the parentheses as +// forming a tuple. +auto TupleExpressionFromParenContents( + int line_num, const ParenContents& paren_contents) + -> const Expression*; // A FieldInitializer represents the initialization of a single tuple field. struct FieldInitializer { - // The field name. For a positional field, this may be empty. + FieldInitializer(std::string name, const Expression* expression) + : name(std::move(name)), expression(expression) {} + + // The field name. Cannot be empty. std::string name; // The expression that initializes the field. @@ -27,7 +45,6 @@ struct FieldInitializer { }; enum class ExpressionKind { - AutoTypeLiteral, BoolTypeLiteral, BoolLiteral, CallExpression, @@ -37,7 +54,6 @@ enum class ExpressionKind { IntTypeLiteral, ContinuationTypeLiteral, // The type of a continuation value. IntLiteral, - BindingExpression, PrimitiveOperatorExpression, TupleLiteral, TypeTypeLiteral, @@ -76,13 +92,6 @@ struct IndexExpression { const Expression* offset; }; -struct BindingExpression { - static constexpr ExpressionKind Kind = ExpressionKind::BindingExpression; - // nullopt represents the `_` placeholder. - std::optional name; - const Expression* type; -}; - struct IntLiteral { static constexpr ExpressionKind Kind = ExpressionKind::IntLiteral; int value; @@ -117,10 +126,6 @@ struct FunctionTypeLiteral { const Expression* return_type; }; -struct AutoTypeLiteral { - static constexpr ExpressionKind Kind = ExpressionKind::AutoTypeLiteral; -}; - struct BoolTypeLiteral { static constexpr ExpressionKind Kind = ExpressionKind::BoolTypeLiteral; }; @@ -141,10 +146,6 @@ struct TypeTypeLiteral { struct Expression { static auto MakeIdentifierExpression(int line_num, std::string var) -> const Expression*; - static auto MakeBindingExpression(int line_num, - std::optional var, - const Expression* type) - -> const Expression*; static auto MakeIntLiteral(int line_num, int i) -> const Expression*; static auto MakeBoolLiteral(int line_num, bool b) -> const Expression*; static auto MakePrimitiveOperatorExpression( @@ -164,13 +165,11 @@ struct Expression { static auto MakeFunctionTypeLiteral(int line_num, const Expression* param, const Expression* ret) -> const Expression*; - static auto MakeAutoTypeLiteral(int line_num) -> const Expression*; static auto MakeContinuationTypeLiteral(int line_num) -> const Expression*; auto GetIdentifierExpression() const -> const IdentifierExpression&; auto GetFieldAccessExpression() const -> const FieldAccessExpression&; auto GetIndexExpression() const -> const IndexExpression&; - auto GetBindingExpression() const -> const BindingExpression&; auto GetIntLiteral() const -> int; auto GetBoolLiteral() const -> bool; auto GetTupleLiteral() const -> const TupleLiteral&; @@ -190,10 +189,10 @@ struct Expression { private: std::variant + BoolTypeLiteral, IntTypeLiteral, ContinuationTypeLiteral, + TypeTypeLiteral> value; }; diff --git a/executable_semantics/ast/expression_test.cpp b/executable_semantics/ast/expression_test.cpp new file mode 100644 index 000000000000..2c7733881de5 --- /dev/null +++ b/executable_semantics/ast/expression_test.cpp @@ -0,0 +1,137 @@ +// 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 + +#include "executable_semantics/ast/expression.h" + +#include + +#include "executable_semantics/syntax/paren_contents.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Carbon { +namespace { + +using testing::ElementsAre; +using testing::IsEmpty; + +// Matches a FieldInitializer named `name` whose `expression` is an +// `IntLiteral` +MATCHER_P(IntFieldNamed, name, "") { + return arg.name == std::string(name) && + arg.expression->tag() == ExpressionKind::IntLiteral; +} + +TEST(ExpressionTest, EmptyAsExpression) { + ParenContents contents = {.elements = {}, + .has_trailing_comma = false}; + const Expression* expression = + ExpressionFromParenContents(/*line_num=*/1, contents); + EXPECT_EQ(expression->line_num, 1); + ASSERT_EQ(expression->tag(), ExpressionKind::TupleLiteral); + EXPECT_THAT(expression->GetTupleLiteral().fields, IsEmpty()); +} + +TEST(ExpressionTest, EmptyAsTuple) { + ParenContents contents = {.elements = {}, + .has_trailing_comma = false}; + const Expression* tuple = + TupleExpressionFromParenContents(/*line_num=*/1, contents); + EXPECT_EQ(tuple->line_num, 1); + ASSERT_EQ(tuple->tag(), ExpressionKind::TupleLiteral); + EXPECT_THAT(tuple->GetTupleLiteral().fields, IsEmpty()); +} + +TEST(ExpressionTest, UnaryNoCommaAsExpression) { + // Equivalent to a code fragment like + // ``` + // ( + // 42 + // ) + // ``` + ParenContents contents = { + .elements = {{.name = std::nullopt, + .term = Expression::MakeIntLiteral(/*line_num=*/2, 42)}}, + .has_trailing_comma = false}; + + const Expression* expression = + ExpressionFromParenContents(/*line_num=*/1, contents); + EXPECT_EQ(expression->line_num, 2); + ASSERT_EQ(expression->tag(), ExpressionKind::IntLiteral); +} + +TEST(ExpressionTest, UnaryNoCommaAsTuple) { + ParenContents contents = { + .elements = {{.name = std::nullopt, + .term = Expression::MakeIntLiteral(/*line_num=*/2, 42)}}, + .has_trailing_comma = false}; + + const Expression* tuple = + TupleExpressionFromParenContents(/*line_num=*/1, contents); + EXPECT_EQ(tuple->line_num, 1); + ASSERT_EQ(tuple->tag(), ExpressionKind::TupleLiteral); + EXPECT_THAT(tuple->GetTupleLiteral().fields, ElementsAre(IntFieldNamed("0"))); +} + +TEST(ExpressionTest, UnaryWithCommaAsExpression) { + ParenContents contents = { + .elements = {{.name = std::nullopt, + .term = Expression::MakeIntLiteral(/*line_num=*/2, 42)}}, + .has_trailing_comma = true}; + + const Expression* expression = + ExpressionFromParenContents(/*line_num=*/1, contents); + EXPECT_EQ(expression->line_num, 1); + ASSERT_EQ(expression->tag(), ExpressionKind::TupleLiteral); + EXPECT_THAT(expression->GetTupleLiteral().fields, + ElementsAre(IntFieldNamed("0"))); +} + +TEST(ExpressionTest, UnaryWithCommaAsTuple) { + ParenContents contents = { + .elements = {{.name = std::nullopt, + .term = Expression::MakeIntLiteral(/*line_num=*/2, 42)}}, + .has_trailing_comma = true}; + + const Expression* tuple = + TupleExpressionFromParenContents(/*line_num=*/1, contents); + EXPECT_EQ(tuple->line_num, 1); + ASSERT_EQ(tuple->tag(), ExpressionKind::TupleLiteral); + EXPECT_THAT(tuple->GetTupleLiteral().fields, ElementsAre(IntFieldNamed("0"))); +} + +TEST(ExpressionTest, BinaryAsExpression) { + ParenContents contents = { + .elements = {{.name = std::nullopt, + .term = Expression::MakeIntLiteral(/*line_num=*/2, 42)}, + {.name = std::nullopt, + .term = Expression::MakeIntLiteral(/*line_num=*/3, 42)}}, + .has_trailing_comma = true}; + + const Expression* expression = + ExpressionFromParenContents(/*line_num=*/1, contents); + EXPECT_EQ(expression->line_num, 1); + ASSERT_EQ(expression->tag(), ExpressionKind::TupleLiteral); + EXPECT_THAT(expression->GetTupleLiteral().fields, + ElementsAre(IntFieldNamed("0"), IntFieldNamed("1"))); +} + +TEST(ExpressionTest, BinaryAsTuple) { + ParenContents contents = { + .elements = {{.name = std::nullopt, + .term = Expression::MakeIntLiteral(/*line_num=*/2, 42)}, + {.name = std::nullopt, + .term = Expression::MakeIntLiteral(/*line_num=*/3, 42)}}, + .has_trailing_comma = true}; + + const Expression* tuple = + TupleExpressionFromParenContents(/*line_num=*/1, contents); + EXPECT_EQ(tuple->line_num, 1); + ASSERT_EQ(tuple->tag(), ExpressionKind::TupleLiteral); + EXPECT_THAT(tuple->GetTupleLiteral().fields, + ElementsAre(IntFieldNamed("0"), IntFieldNamed("1"))); +} + +} // namespace +} // namespace Carbon diff --git a/executable_semantics/ast/function_definition.h b/executable_semantics/ast/function_definition.h index 8ea810077db5..9a8e9f59d15c 100644 --- a/executable_semantics/ast/function_definition.h +++ b/executable_semantics/ast/function_definition.h @@ -7,6 +7,7 @@ #include "common/ostream.h" #include "executable_semantics/ast/expression.h" +#include "executable_semantics/ast/pattern.h" #include "executable_semantics/ast/statement.h" #include "llvm/Support/Compiler.h" @@ -23,8 +24,8 @@ struct FunctionDefinition { FunctionDefinition() = default; FunctionDefinition(int line_num, std::string name, std::vector deduced_params, - const Expression* param_pattern, - const Expression* return_type, const Statement* body) + const TuplePattern* param_pattern, + const Pattern* return_type, const Statement* body) : line_num(line_num), name(std::move(name)), deduced_parameters(deduced_params), @@ -39,8 +40,8 @@ struct FunctionDefinition { int line_num; std::string name; std::vector deduced_parameters; - const Expression* param_pattern; - const Expression* return_type; + const TuplePattern* param_pattern; + const Pattern* return_type; const Statement* body; }; diff --git a/executable_semantics/ast/member.cpp b/executable_semantics/ast/member.cpp index 4032a0617b50..b7b14a24f429 100644 --- a/executable_semantics/ast/member.cpp +++ b/executable_semantics/ast/member.cpp @@ -6,11 +6,11 @@ namespace Carbon { -auto Member::MakeFieldMember(int line_num, std::string name, - const Expression* type) -> Member* { +auto Member::MakeFieldMember(int line_num, const BindingPattern* binding) + -> Member* { auto m = new Member(); m->line_num = line_num; - m->value = FieldMember({.name = std::move(name), .type = type}); + m->value = FieldMember({.binding = binding}); return m; } @@ -22,7 +22,7 @@ void Member::Print(llvm::raw_ostream& out) const { switch (tag()) { case MemberKind::FieldMember: const auto& field = GetFieldMember(); - out << "var " << field.name << " : " << *field.type << ";\n"; + out << "var " << field.binding << ";\n"; break; } } diff --git a/executable_semantics/ast/member.h b/executable_semantics/ast/member.h index 5b0bbbd84ec7..acfd078db154 100644 --- a/executable_semantics/ast/member.h +++ b/executable_semantics/ast/member.h @@ -9,6 +9,7 @@ #include "common/ostream.h" #include "executable_semantics/ast/expression.h" +#include "executable_semantics/ast/pattern.h" #include "llvm/Support/Compiler.h" namespace Carbon { @@ -17,13 +18,15 @@ enum class MemberKind { FieldMember }; struct FieldMember { static constexpr MemberKind Kind = MemberKind::FieldMember; - std::string name; - const Expression* type; + // TODO: split this into a non-optional name and a type, initialized by + // a constructor that takes a BindingPattern and handles errors like a + // missing name. + const BindingPattern* binding; }; struct Member { - static auto MakeFieldMember(int line_num, std::string name, - const Expression* type) -> Member*; + static auto MakeFieldMember(int line_num, const BindingPattern* binding) + -> Member*; auto GetFieldMember() const -> const FieldMember&; diff --git a/executable_semantics/ast/pattern.cpp b/executable_semantics/ast/pattern.cpp new file mode 100644 index 000000000000..2d608a10bbe3 --- /dev/null +++ b/executable_semantics/ast/pattern.cpp @@ -0,0 +1,106 @@ +// 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 + +#include "executable_semantics/ast/pattern.h" + +#include + +#include "common/ostream.h" +#include "executable_semantics/ast/expression.h" +#include "executable_semantics/common/error.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/Support/Casting.h" + +namespace Carbon { + +using llvm::cast; + +void Pattern::Print(llvm::raw_ostream& out) const { + switch (Tag()) { + case Kind::AutoPattern: + out << "auto"; + break; + case Kind::BindingPattern: { + const auto& binding = cast(*this); + if (binding.Name().has_value()) { + out << *binding.Name(); + } else { + out << "_"; + } + out << ": " << *binding.Type(); + break; + } + case Kind::TuplePattern: { + const auto& tuple = cast(*this); + out << "("; + llvm::ListSeparator sep; + for (const TuplePattern::Field& field : tuple.Fields()) { + out << sep << field.name << " = " << field.pattern; + } + out << ")"; + break; + } + case Kind::AlternativePattern: { + const auto& alternative = cast(*this); + out << alternative.ChoiceType() << "." << alternative.AlternativeName() + << alternative.Arguments(); + break; + } + case Kind::ExpressionPattern: + out << cast(*this).Expression(); + break; + } +} + +TuplePattern::TuplePattern(const Expression* tuple_literal) + : Pattern(Kind::TuplePattern, tuple_literal->line_num) { + const auto& tuple = tuple_literal->GetTupleLiteral(); + for (const FieldInitializer& init : tuple.fields) { + fields.push_back(Field(init.name, new ExpressionPattern(init.expression))); + } +} + +auto PatternFromParenContents(int line_num, + const ParenContents& paren_contents) + -> const Pattern* { + std::optional single_term = paren_contents.SingleTerm(); + if (single_term.has_value()) { + return *single_term; + } else { + return TuplePatternFromParenContents(line_num, paren_contents); + } +} + +auto TuplePatternFromParenContents(int line_num, + const ParenContents& paren_contents) + -> const TuplePattern* { + return new TuplePattern( + line_num, paren_contents.TupleElements(line_num)); +} + +AlternativePattern::AlternativePattern(int line_num, + const Expression* alternative, + const TuplePattern* arguments) + : Pattern(Kind::AlternativePattern, line_num), arguments(arguments) { + if (alternative->tag() != ExpressionKind::FieldAccessExpression) { + FATAL_USER_ERROR(alternative->line_num) + << "Alternative pattern must have the form of a field access."; + } + const auto& field_access = alternative->GetFieldAccessExpression(); + choice_type = field_access.aggregate; + alternative_name = field_access.field; +} + +auto ParenExpressionToParenPattern(const ParenContents& contents) + -> ParenContents { + ParenContents result = { + .elements = {}, .has_trailing_comma = contents.has_trailing_comma}; + for (const auto& element : contents.elements) { + result.elements.push_back( + {.name = element.name, .term = new ExpressionPattern(element.term)}); + } + return result; +} + +} // namespace Carbon diff --git a/executable_semantics/ast/pattern.h b/executable_semantics/ast/pattern.h new file mode 100644 index 000000000000..fcee26ccb66a --- /dev/null +++ b/executable_semantics/ast/pattern.h @@ -0,0 +1,199 @@ +// 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 + +#ifndef EXECUTABLE_SEMANTICS_AST_PATTERN_H_ +#define EXECUTABLE_SEMANTICS_AST_PATTERN_H_ + +#include +#include +#include + +#include "common/ostream.h" +#include "executable_semantics/ast/expression.h" + +namespace Carbon { + +// Abstract base class of all AST nodes representing patterns. +// +// Pattern and its derived classes support LLVM-style RTTI, including +// llvm::isa, llvm::cast, and llvm::dyn_cast. To support this, every +// class derived from Pattern must provide a `classof` operation, and +// every concrete derived class must have a corresponding enumerator +// in `Kind`; see https://llvm.org/docs/HowToSetUpLLVMStyleRTTI.html for +// details. +class Pattern { + public: + enum class Kind { + AutoPattern, + BindingPattern, + TuplePattern, + AlternativePattern, + ExpressionPattern, + }; + + Pattern(const Pattern&) = delete; + Pattern& operator=(const Pattern&) = delete; + + // Returns the enumerator corresponding to the most-derived type of this + // object. + auto Tag() const -> Kind { return tag; } + + auto LineNumber() const -> int { return line_num; } + + void Print(llvm::raw_ostream& out) const; + + protected: + // Constructs a Pattern representing syntax at the given line number. + // `tag` must be the enumerator corresponding to the most-derived type being + // constructed. + Pattern(Kind tag, int line_num) : tag(tag), line_num(line_num) {} + + private: + const Kind tag; + int line_num; +}; + +// A pattern consisting of the `auto` keyword. +class AutoPattern : public Pattern { + public: + explicit AutoPattern(int line_num) : Pattern(Kind::AutoPattern, line_num) {} + + static auto classof(const Pattern* pattern) -> bool { + return pattern->Tag() == Kind::AutoPattern; + } +}; + +// A pattern that matches a value of a specified type, and optionally binds +// a name to it. +class BindingPattern : public Pattern { + public: + BindingPattern(int line_num, std::optional name, + const Pattern* type) + : Pattern(Kind::BindingPattern, line_num), + name(std::move(name)), + type(type) {} + + static auto classof(const Pattern* pattern) -> bool { + return pattern->Tag() == Kind::BindingPattern; + } + + // The name this pattern binds, if any. + auto Name() const -> const std::optional& { return name; } + + // The pattern specifying the type of values that this pattern matches. + auto Type() const -> const Pattern* { return type; } + + private: + std::optional name; + const Pattern* type; +}; + +// A pattern that matches a tuple value field-wise. +class TuplePattern : public Pattern { + public: + // Represents a portion of a tuple pattern corresponding to a single field. + struct Field { + Field(std::string name, const Pattern* pattern) + : name(std::move(name)), pattern(pattern) {} + + // The field name. Cannot be empty + std::string name; + + // The pattern the field must match. + const Pattern* pattern; + }; + + TuplePattern(int line_num, std::vector fields) + : Pattern(Kind::TuplePattern, line_num), fields(std::move(fields)) {} + + // Converts tuple_literal to a TuplePattern, by wrapping each field in an + // ExpressionPattern. + // + // REQUIRES: tuple_literal->Tag() == ExpressionKind::TupleLiteral + explicit TuplePattern(const Expression* tuple_literal); + + static auto classof(const Pattern* pattern) -> bool { + return pattern->Tag() == Kind::TuplePattern; + } + + auto Fields() const -> const std::vector& { return fields; } + + private: + std::vector fields; +}; + +// Converts paren_contents to a Pattern, interpreting the parentheses as +// grouping if their contents permit that interpretation, or as forming a +// tuple otherwise. +auto PatternFromParenContents(int line_num, + const ParenContents& paren_contents) + -> const Pattern*; + +// Converts paren_contents to a TuplePattern, interpreting the parentheses as +// forming a tuple. +auto TuplePatternFromParenContents(int line_num, + const ParenContents& paren_contents) + -> const TuplePattern*; + +// Converts `contents` to ParenContents by replacing each Expression +// with an ExpressionPattern. +auto ParenExpressionToParenPattern(const ParenContents& contents) + -> ParenContents; + +// A pattern that matches an alternative of a choice type. +class AlternativePattern : public Pattern { + public: + // Constructs an AlternativePattern that matches a value of the type + // specified by choice_type if it represents an alternative named + // alternative_name, and its arguments match `arguments`. + AlternativePattern(int line_num, const Expression* choice_type, + std::string alternative_name, + const TuplePattern* arguments) + : Pattern(Kind::AlternativePattern, line_num), + choice_type(choice_type), + alternative_name(std::move(alternative_name)), + arguments(arguments) {} + + // Constructs an AlternativePattern that matches the alternative specified + // by `alternative`, if its arguments match `arguments`. + AlternativePattern(int line_num, const Expression* alternative, + const TuplePattern* arguments); + + static auto classof(const Pattern* pattern) -> bool { + return pattern->Tag() == Kind::AlternativePattern; + } + + auto ChoiceType() const -> const Expression* { return choice_type; } + auto AlternativeName() const -> const std::string& { + return alternative_name; + } + auto Arguments() const -> const TuplePattern* { return arguments; } + + private: + const Expression* choice_type; + std::string alternative_name; + const TuplePattern* arguments; +}; + +// A pattern that matches a value if it is equal to the value of a given +// expression. +class ExpressionPattern : public Pattern { + public: + ExpressionPattern(const Expression* expression) + : Pattern(Kind::ExpressionPattern, expression->line_num), + expression(expression) {} + + static auto classof(const Pattern* pattern) -> bool { + return pattern->Tag() == Kind::ExpressionPattern; + } + + auto Expression() const -> const Expression* { return expression; } + + private: + const Carbon::Expression* expression; +}; + +} // namespace Carbon + +#endif // EXECUTABLE_SEMANTICS_AST_PATTERN_H_ diff --git a/executable_semantics/ast/pattern_test.cpp b/executable_semantics/ast/pattern_test.cpp new file mode 100644 index 000000000000..1c53e5a3a5f8 --- /dev/null +++ b/executable_semantics/ast/pattern_test.cpp @@ -0,0 +1,130 @@ +// 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 + +#include "executable_semantics/ast/pattern.h" + +#include "executable_semantics/ast/expression.h" +#include "executable_semantics/syntax/paren_contents.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "llvm/Support/Casting.h" + +namespace Carbon { +namespace { + +using llvm::cast; +using llvm::isa; +using testing::ElementsAre; +using testing::IsEmpty; + +// Matches a TuplePattern::Field named `name` whose `pattern` is an +// `AutoPattern`. +MATCHER_P(AutoFieldNamed, name, "") { + return arg.name == std::string(name) && isa(arg.pattern); +} + +TEST(PatternTest, EmptyAsPattern) { + ParenContents contents = {.elements = {}, + .has_trailing_comma = false}; + const Pattern* pattern = PatternFromParenContents(/*line_num=*/1, contents); + EXPECT_EQ(pattern->LineNumber(), 1); + ASSERT_TRUE(isa(pattern)); + EXPECT_THAT(cast(pattern)->Fields(), IsEmpty()); +} + +TEST(PatternTest, EmptyAsTuplePattern) { + ParenContents contents = {.elements = {}, + .has_trailing_comma = false}; + const TuplePattern* tuple = + TuplePatternFromParenContents(/*line_num=*/1, contents); + EXPECT_EQ(tuple->LineNumber(), 1); + EXPECT_THAT(tuple->Fields(), IsEmpty()); +} + +TEST(PatternTest, UnaryNoCommaAsPattern) { + // Equivalent to a code fragment like + // ``` + // ( + // auto + // ) + // ``` + ParenContents contents = { + .elements = {{.name = std::nullopt, + .term = new AutoPattern(/*line_num=*/2)}}, + .has_trailing_comma = false}; + + const Pattern* pattern = PatternFromParenContents(/*line_num=*/1, contents); + EXPECT_EQ(pattern->LineNumber(), 2); + ASSERT_TRUE(isa(pattern)); +} + +TEST(PatternTest, UnaryNoCommaAsTuplePattern) { + ParenContents contents = { + .elements = {{.name = std::nullopt, + .term = new AutoPattern(/*line_num=*/2)}}, + .has_trailing_comma = false}; + + const TuplePattern* tuple = + TuplePatternFromParenContents(/*line_num=*/1, contents); + EXPECT_EQ(tuple->LineNumber(), 1); + EXPECT_THAT(tuple->Fields(), ElementsAre(AutoFieldNamed("0"))); +} + +TEST(PatternTest, UnaryWithCommaAsPattern) { + ParenContents contents = { + .elements = {{.name = std::nullopt, + .term = new AutoPattern(/*line_num=*/2)}}, + .has_trailing_comma = true}; + + const Pattern* pattern = PatternFromParenContents(/*line_num=*/1, contents); + EXPECT_EQ(pattern->LineNumber(), 1); + ASSERT_TRUE(isa(pattern)); + EXPECT_THAT(cast(pattern)->Fields(), + ElementsAre(AutoFieldNamed("0"))); +} + +TEST(PatternTest, UnaryWithCommaAsTuplePattern) { + ParenContents contents = { + .elements = {{.name = std::nullopt, + .term = new AutoPattern(/*line_num=*/2)}}, + .has_trailing_comma = true}; + + const TuplePattern* tuple = + TuplePatternFromParenContents(/*line_num=*/1, contents); + EXPECT_EQ(tuple->LineNumber(), 1); + EXPECT_THAT(tuple->Fields(), ElementsAre(AutoFieldNamed("0"))); +} + +TEST(PatternTest, BinaryAsPattern) { + ParenContents contents = { + .elements = {{.name = std::nullopt, + .term = new AutoPattern(/*line_num=*/2)}, + {.name = std::nullopt, + .term = new AutoPattern(/*line_num=*/3)}}, + .has_trailing_comma = true}; + + const Pattern* pattern = PatternFromParenContents(/*line_num=*/1, contents); + EXPECT_EQ(pattern->LineNumber(), 1); + ASSERT_TRUE(isa(pattern)); + EXPECT_THAT(cast(pattern)->Fields(), + ElementsAre(AutoFieldNamed("0"), AutoFieldNamed("1"))); +} + +TEST(PatternTest, BinaryAsTuplePattern) { + ParenContents contents = { + .elements = {{.name = std::nullopt, + .term = new AutoPattern(/*line_num=*/2)}, + {.name = std::nullopt, + .term = new AutoPattern(/*line_num=*/3)}}, + .has_trailing_comma = true}; + + const TuplePattern* tuple = + TuplePatternFromParenContents(/*line_num=*/1, contents); + EXPECT_EQ(tuple->LineNumber(), 1); + EXPECT_THAT(tuple->Fields(), + ElementsAre(AutoFieldNamed("0"), AutoFieldNamed("1"))); +} + +} // namespace +} // namespace Carbon diff --git a/executable_semantics/ast/statement.cpp b/executable_semantics/ast/statement.cpp index 066ef2226e5a..02446b5090be 100644 --- a/executable_semantics/ast/statement.cpp +++ b/executable_semantics/ast/statement.cpp @@ -76,7 +76,7 @@ auto Statement::MakeAssign(int line_num, const Expression* lhs, return s; } -auto Statement::MakeVariableDefinition(int line_num, const Expression* pat, +auto Statement::MakeVariableDefinition(int line_num, const Pattern* pat, const Expression* init) -> const Statement* { auto* s = new Statement(); @@ -142,7 +142,7 @@ auto Statement::MakeBlock(int line_num, const Statement* stmt) auto Statement::MakeMatch( int line_num, const Expression* exp, - std::list>* clauses) + std::list>* clauses) -> const Statement* { auto* s = new Statement(); s->line_num = line_num; diff --git a/executable_semantics/ast/statement.h b/executable_semantics/ast/statement.h index 6cb54c72170d..e9e2cedf2ae7 100644 --- a/executable_semantics/ast/statement.h +++ b/executable_semantics/ast/statement.h @@ -9,6 +9,7 @@ #include "common/ostream.h" #include "executable_semantics/ast/expression.h" +#include "executable_semantics/ast/pattern.h" #include "llvm/Support/Compiler.h" namespace Carbon { @@ -45,7 +46,7 @@ struct Assign { struct VariableDefinition { static constexpr StatementKind Kind = StatementKind::VariableDefinition; - const Expression* pat; + const Pattern* pat; const Expression* init; }; @@ -89,7 +90,7 @@ struct Continue { struct Match { static constexpr StatementKind Kind = StatementKind::Match; const Expression* exp; - std::list>* clauses; + std::list>* clauses; }; struct Continuation { @@ -113,7 +114,7 @@ struct Statement { -> const Statement*; static auto MakeAssign(int line_num, const Expression* lhs, const Expression* rhs) -> const Statement*; - static auto MakeVariableDefinition(int line_num, const Expression* pat, + static auto MakeVariableDefinition(int line_num, const Pattern* pat, const Expression* init) -> const Statement*; static auto MakeIf(int line_num, const Expression* cond, @@ -129,7 +130,7 @@ struct Statement { static auto MakeContinue(int line_num) -> const Statement*; static auto MakeMatch( int line_num, const Expression* exp, - std::list>* clauses) + std::list>* clauses) -> const Statement*; // Returns an AST node for a continuation statement give its line number and // contituent parts. diff --git a/executable_semantics/interpreter/BUILD b/executable_semantics/interpreter/BUILD index 259dd15b1b63..a40d659c8ba5 100644 --- a/executable_semantics/interpreter/BUILD +++ b/executable_semantics/interpreter/BUILD @@ -98,6 +98,7 @@ cc_library( "//executable_semantics/ast:expression", "//executable_semantics/ast:function_definition", "//executable_semantics/common:tracing_flag", + "@llvm-project//llvm:Support", ], ) @@ -113,6 +114,7 @@ cc_library( "//executable_semantics/ast:function_definition", "//executable_semantics/ast:statement", "//executable_semantics/common:tracing_flag", + "@llvm-project//llvm:Support", ], ) diff --git a/executable_semantics/interpreter/action.cpp b/executable_semantics/interpreter/action.cpp index d25e46486cc2..c73161b548c5 100644 --- a/executable_semantics/interpreter/action.cpp +++ b/executable_semantics/interpreter/action.cpp @@ -29,6 +29,12 @@ auto Action::MakeExpressionAction(const Expression* e) -> Action* { return act; } +auto Action::MakePatternAction(const Pattern* p) -> Action* { + auto* act = new Action(); + act->value = PatternAction({.pattern = p}); + return act; +} + auto Action::MakeStatementAction(const Statement* s) -> Action* { auto* act = new Action(); act->value = StatementAction({.stmt = s}); @@ -49,6 +55,10 @@ auto Action::GetExpressionAction() const -> const ExpressionAction& { return std::get(value); } +auto Action::GetPatternAction() const -> const PatternAction& { + return std::get(value); +} + auto Action::GetStatementAction() const -> const StatementAction& { return std::get(value); } @@ -65,6 +75,9 @@ void Action::Print(llvm::raw_ostream& out) const { case ActionKind::ExpressionAction: out << *GetExpressionAction().exp; break; + case ActionKind::PatternAction: + out << *GetPatternAction().pattern; + break; case ActionKind::StatementAction: GetStatementAction().stmt->PrintDepth(1, out); break; diff --git a/executable_semantics/interpreter/action.h b/executable_semantics/interpreter/action.h index 52fc93efe3c5..ac4f0f4b7f96 100644 --- a/executable_semantics/interpreter/action.h +++ b/executable_semantics/interpreter/action.h @@ -9,6 +9,7 @@ #include "common/ostream.h" #include "executable_semantics/ast/expression.h" +#include "executable_semantics/ast/pattern.h" #include "executable_semantics/ast/statement.h" #include "executable_semantics/interpreter/stack.h" #include "executable_semantics/interpreter/value.h" @@ -19,6 +20,7 @@ namespace Carbon { enum class ActionKind { LValAction, ExpressionAction, + PatternAction, StatementAction, ValAction, }; @@ -33,6 +35,11 @@ struct ExpressionAction { const Expression* exp; }; +struct PatternAction { + static constexpr ActionKind Kind = ActionKind::PatternAction; + const Pattern* pattern; +}; + struct StatementAction { static constexpr ActionKind Kind = ActionKind::StatementAction; const Statement* stmt; @@ -46,6 +53,7 @@ struct ValAction { struct Action { static auto MakeLValAction(const Expression* e) -> Action*; static auto MakeExpressionAction(const Expression* e) -> Action*; + static auto MakePatternAction(const Pattern* p) -> Action*; static auto MakeStatementAction(const Statement* s) -> Action*; static auto MakeValAction(const Value* v) -> Action*; @@ -53,6 +61,7 @@ struct Action { auto GetLValAction() const -> const LValAction&; auto GetExpressionAction() const -> const ExpressionAction&; + auto GetPatternAction() const -> const PatternAction&; auto GetStatementAction() const -> const StatementAction&; auto GetValAction() const -> const ValAction&; @@ -75,7 +84,9 @@ struct Action { std::vector results; private: - std::variant value; + std::variant + value; }; } // namespace Carbon diff --git a/executable_semantics/interpreter/interpreter.cpp b/executable_semantics/interpreter/interpreter.cpp index 4e6359b80f7a..4e6fc2841fde 100644 --- a/executable_semantics/interpreter/interpreter.cpp +++ b/executable_semantics/interpreter/interpreter.cpp @@ -20,6 +20,9 @@ #include "executable_semantics/interpreter/frame.h" #include "executable_semantics/interpreter/stack.h" #include "llvm/ADT/StringExtras.h" +#include "llvm/Support/Casting.h" + +using llvm::cast; namespace Carbon { @@ -114,7 +117,7 @@ void InitEnv(const Declaration& d, Env* env) { state->heap.AllocateValue(Value::MakeVariableType(deduced.name)); new_env.Set(deduced.name, a); } - auto pt = InterpExp(new_env, func_def.param_pattern); + auto pt = InterpPattern(new_env, func_def.param_pattern); auto f = Value::MakeFunctionValue(func_def.name, pt, func_def.body); Address a = state->heap.AllocateValue(f); env->Set(func_def.name, a); @@ -128,9 +131,11 @@ void InitEnv(const Declaration& d, Env* env) { for (const Member* m : struct_def.members) { switch (m->tag()) { case MemberKind::FieldMember: { - const auto& field = m->GetFieldMember(); - auto t = InterpExp(Env(), field.type); - fields.push_back(make_pair(field.name, t)); + const BindingPattern* binding = m->GetFieldMember().binding; + const Expression* type_expression = + cast(binding->Type())->Expression(); + auto type = InterpExp(Env(), type_expression); + fields.push_back(make_pair(*binding->Name(), type)); break; } } @@ -161,7 +166,7 @@ void InitEnv(const Declaration& d, Env* env) { // result of evaluating the initializer. auto v = InterpExp(*env, var.initializer); Address a = state->heap.AllocateValue(v); - env->Set(var.name, a); + env->Set(*var.binding->Name(), a); break; } } @@ -502,9 +507,7 @@ void StepLvalue() { case ExpressionKind::BoolTypeLiteral: case ExpressionKind::TypeTypeLiteral: case ExpressionKind::FunctionTypeLiteral: - case ExpressionKind::AutoTypeLiteral: - case ExpressionKind::ContinuationTypeLiteral: - case ExpressionKind::BindingExpression: { + case ExpressionKind::ContinuationTypeLiteral: { FATAL_RUNTIME_ERROR_NO_LINE() << "Can't treat expression as lvalue: " << *exp; } @@ -521,19 +524,6 @@ void StepExp() { llvm::outs() << "--- step exp " << *exp << " --->\n"; } switch (exp->tag()) { - case ExpressionKind::BindingExpression: { - if (act->pos == 0) { - frame->todo.Push( - Action::MakeExpressionAction(exp->GetBindingExpression().type)); - act->pos++; - } else { - auto v = Value::MakeBindingPlaceholderValue( - exp->GetBindingExpression().name, act->results[0]); - frame->todo.Pop(1); - frame->todo.Push(Action::MakeValAction(v)); - } - break; - } case ExpressionKind::IndexExpression: { if (act->pos == 0) { // { { e[i] :: C, E, F} :: S, H} @@ -696,13 +686,6 @@ void StepExp() { frame->todo.Push(Action::MakeValAction(v)); break; } - case ExpressionKind::AutoTypeLiteral: { - CHECK(act->pos == 0); - const Value* v = Value::MakeAutoType(); - frame->todo.Pop(1); - frame->todo.Push(Action::MakeValAction(v)); - break; - } case ExpressionKind::TypeTypeLiteral: { CHECK(act->pos == 0); const Value* v = Value::MakeTypeType(); @@ -741,6 +724,91 @@ void StepExp() { } // switch (exp->tag) } +void StepPattern() { + Frame* frame = state->stack.Top(); + Action* act = frame->todo.Top(); + const Pattern* pattern = act->GetPatternAction().pattern; + if (tracing_output) { + llvm::outs() << "--- step pattern " << *pattern << " --->\n"; + } + switch (pattern->Tag()) { + case Pattern::Kind::AutoPattern: { + CHECK(act->pos == 0); + const Value* v = Value::MakeAutoType(); + frame->todo.Pop(1); + frame->todo.Push(Action::MakeValAction(v)); + break; + } + case Pattern::Kind::BindingPattern: { + const auto& binding = cast(*pattern); + if (act->pos == 0) { + frame->todo.Push(Action::MakePatternAction(binding.Type())); + act->pos++; + } else { + auto v = + Value::MakeBindingPlaceholderValue(binding.Name(), act->results[0]); + frame->todo.Pop(1); + frame->todo.Push(Action::MakeValAction(v)); + } + break; + } + case Pattern::Kind::TuplePattern: { + const auto& tuple = cast(*pattern); + if (act->pos == 0) { + if (tuple.Fields().empty()) { + frame->todo.Pop(1); + frame->todo.Push(Action::MakeValAction(Value::MakeTupleValue({}))); + } else { + const Pattern* p1 = tuple.Fields()[0].pattern; + frame->todo.Push(Action::MakePatternAction(p1)); + act->pos++; + } + } else if (act->pos != static_cast(tuple.Fields().size())) { + // { { vk :: (f1=v1,..., fk=[],fk+1=ek+1,...) :: C, E, F} :: S, + // H} + // -> { { ek+1 :: (f1=v1,..., fk=vk, fk+1=[],...) :: C, E, F} :: S, + // H} + const Pattern* elt = tuple.Fields()[act->pos].pattern; + frame->todo.Push(Action::MakePatternAction(elt)); + act->pos++; + } else { + std::vector elements; + for (size_t i = 0; i < tuple.Fields().size(); ++i) { + elements.push_back( + {.name = tuple.Fields()[i].name, .value = act->results[i]}); + } + const Value* tuple_value = Value::MakeTupleValue(std::move(elements)); + frame->todo.Pop(1); + frame->todo.Push(Action::MakeValAction(tuple_value)); + } + break; + } + case Pattern::Kind::AlternativePattern: { + const auto& alternative = cast(*pattern); + if (act->pos == 0) { + frame->todo.Push( + Action::MakeExpressionAction(alternative.ChoiceType())); + act->pos++; + } else if (act->pos == 1) { + frame->todo.Push(Action::MakePatternAction(alternative.Arguments())); + act->pos++; + } else { + CHECK(act->pos == 2); + const auto& choice_type = act->results[0]->GetChoiceType(); + frame->todo.Pop(1); + frame->todo.Push(Action::MakeValAction(Value::MakeAlternativeValue( + alternative.AlternativeName(), choice_type.name, act->results[1]))); + } + break; + } + case Pattern::Kind::ExpressionPattern: + frame->todo.Pop(1); + frame->todo.Push(Action::MakeExpressionAction( + cast(pattern)->Expression())); + break; + } +} + auto IsWhileAct(Action* act) -> bool { switch (act->tag()) { case ActionKind::StatementAction: @@ -810,7 +878,7 @@ void StepStmt() { // start interpreting the pattern of the clause // { {v :: (match ([]) ...) :: C, E, F} :: S, H} // -> { {pi :: (match ([]) ...) :: C, E, F} :: S, H} - frame->todo.Push(Action::MakeExpressionAction(c->first)); + frame->todo.Push(Action::MakePatternAction(c->first)); act->pos++; } else { // try to match auto v = act->results[0]; @@ -916,7 +984,7 @@ void StepStmt() { act->pos++; } else if (act->pos == 1) { frame->todo.Push( - Action::MakeExpressionAction(stmt->GetVariableDefinition().pat)); + Action::MakePatternAction(stmt->GetVariableDefinition().pat)); act->pos++; } else if (act->pos == 2) { // { { v :: (x = []) :: C, E, F} :: S, H} @@ -1097,6 +1165,9 @@ void Step() { case ActionKind::ExpressionAction: StepExp(); break; + case ActionKind::PatternAction: + StepPattern(); + break; case ActionKind::StatementAction: StepStmt(); break; @@ -1150,4 +1221,19 @@ auto InterpExp(Env values, const Expression* e) -> const Value* { return v; } +// Interpret a pattern at compile-time. +auto InterpPattern(Env values, const Pattern* p) -> const Value* { + auto todo = Stack(Action::MakePatternAction(p)); + auto* scope = new Scope(values, std::list()); + auto* frame = new Frame("InterpPattern", Stack(scope), todo); + state->stack = Stack(frame); + + while (state->stack.Count() > 1 || state->stack.Top()->todo.Count() > 1 || + state->stack.Top()->todo.Top()->tag() != ActionKind::ValAction) { + Step(); + } + const Value* v = state->stack.Top()->todo.Top()->GetValAction().val; + return v; +} + } // namespace Carbon diff --git a/executable_semantics/interpreter/interpreter.h b/executable_semantics/interpreter/interpreter.h index 6541b1ff068b..a5910aada93b 100644 --- a/executable_semantics/interpreter/interpreter.h +++ b/executable_semantics/interpreter/interpreter.h @@ -11,6 +11,8 @@ #include "common/ostream.h" #include "executable_semantics/ast/declaration.h" +#include "executable_semantics/ast/expression.h" +#include "executable_semantics/ast/pattern.h" #include "executable_semantics/interpreter/frame.h" #include "executable_semantics/interpreter/heap.h" #include "executable_semantics/interpreter/stack.h" @@ -35,6 +37,7 @@ void PrintEnv(Env values); auto InterpProgram(std::list* fs) -> int; auto InterpExp(Env values, const Expression* e) -> const Value*; +auto InterpPattern(Env values, const Pattern* p) -> const Value*; } // namespace Carbon diff --git a/executable_semantics/interpreter/typecheck.cpp b/executable_semantics/interpreter/typecheck.cpp index 1c2b09b0e0f7..afede2623823 100644 --- a/executable_semantics/interpreter/typecheck.cpp +++ b/executable_semantics/interpreter/typecheck.cpp @@ -10,10 +10,16 @@ #include #include +#include "common/ostream.h" #include "executable_semantics/ast/function_definition.h" #include "executable_semantics/common/error.h" #include "executable_semantics/common/tracing_flag.h" #include "executable_semantics/interpreter/interpreter.h" +#include "executable_semantics/interpreter/value.h" +#include "llvm/Support/Casting.h" + +using llvm::cast; +using llvm::dyn_cast; namespace Carbon { @@ -53,8 +59,8 @@ auto ReifyType(const Value* t, int line_num) -> const Expression* { case ValKind::TupleValue: { std::vector args; for (const TupleElement& field : t->GetTupleValue().elements) { - args.push_back({.name = field.name, - .expression = ReifyType(field.value, line_num)}); + args.push_back( + FieldInitializer(field.name, ReifyType(field.value, line_num))); } return Expression::MakeTupleLiteral(0, args); } @@ -230,58 +236,14 @@ auto Substitute(TypeEnv dict, const Value* type) -> const Value* { // types maps variable names to the type of their run-time value. // values maps variable names to their compile-time values. It is not // directly used in this function but is passed to InterExp. -// expected is the type that this expression is expected to have. -// This parameter is non-null when the expression is in a pattern context -// and it is used to implement `auto`, otherwise it is null. -// context says what kind of position this expression is nested in, -// whether it's a position that expects a value, a pattern, or a type. -auto TypeCheckExp(const Expression* e, TypeEnv types, Env values, - const Value* expected, TCContext context) -> TCResult { +auto TypeCheckExp(const Expression* e, TypeEnv types, Env values) + -> TCExpression { if (tracing_output) { - switch (context) { - case TCContext::ValueContext: - llvm::outs() << "checking expression "; - break; - case TCContext::PatternContext: - llvm::outs() << "checking pattern, "; - if (expected) { - llvm::outs() << "expecting " << *expected; - } - llvm::outs() << ", "; - break; - case TCContext::TypeContext: - llvm::outs() << "checking type "; - break; - } - llvm::outs() << *e << "\n"; + llvm::outs() << "checking expression " << *e << "\n"; } switch (e->tag()) { - case ExpressionKind::BindingExpression: { - if (context != TCContext::PatternContext) { - FATAL_COMPILATION_ERROR(e->line_num) - << "pattern variables are only allowed in pattern context"; - } - auto t = InterpExp(values, e->GetBindingExpression().type); - if (t->tag() == ValKind::AutoType) { - if (expected == nullptr) { - FATAL_COMPILATION_ERROR(e->line_num) << " auto not allowed here"; - } else { - t = expected; - } - } else if (expected) { - ExpectType(e->line_num, "pattern variable", t, expected); - } - const std::optional& name = e->GetBindingExpression().name; - auto new_e = Expression::MakeBindingExpression(e->line_num, name, - ReifyType(t, e->line_num)); - if (name.has_value()) { - types.Set(*name, t); - } - return TCResult(new_e, t, types); - } case ExpressionKind::IndexExpression: { - auto res = TypeCheckExp(e->GetIndexExpression().aggregate, types, values, - nullptr, TCContext::ValueContext); + auto res = TypeCheckExp(e->GetIndexExpression().aggregate, types, values); auto t = res.type; switch (t->tag()) { case ValKind::TupleValue: { @@ -295,7 +257,7 @@ auto TypeCheckExp(const Expression* e, TypeEnv types, Env values, } auto new_e = Expression::MakeIndexExpression( e->line_num, res.exp, Expression::MakeIntLiteral(e->line_num, i)); - return TCResult(new_e, field_t, res.types); + return TCExpression(new_e, field_t, res.types); } default: FATAL_COMPILATION_ERROR(e->line_num) << "expected a tuple"; @@ -305,39 +267,21 @@ auto TypeCheckExp(const Expression* e, TypeEnv types, Env values, std::vector new_args; std::vector arg_types; auto new_types = types; - if (expected && expected->tag() != ValKind::TupleValue) { - FATAL_COMPILATION_ERROR(e->line_num) << "didn't expect a tuple"; - } - if (expected && e->GetTupleLiteral().fields.size() != - expected->GetTupleValue().elements.size()) { - FATAL_COMPILATION_ERROR(e->line_num) << "tuples of different length"; - } int i = 0; for (auto arg = e->GetTupleLiteral().fields.begin(); arg != e->GetTupleLiteral().fields.end(); ++arg, ++i) { - const Value* arg_expected = nullptr; - if (expected && expected->tag() == ValKind::TupleValue) { - if (expected->GetTupleValue().elements[i].name != arg->name) { - FATAL_COMPILATION_ERROR(e->line_num) - << "field names do not match, expected " - << expected->GetTupleValue().elements[i].name << " but got " - << arg->name; - } - arg_expected = expected->GetTupleValue().elements[i].value; - } - auto arg_res = TypeCheckExp(arg->expression, new_types, values, - arg_expected, context); + auto arg_res = TypeCheckExp(arg->expression, new_types, values); new_types = arg_res.types; - new_args.push_back({.name = arg->name, .expression = arg_res.exp}); + new_args.push_back(FieldInitializer(arg->name, arg_res.exp)); arg_types.push_back({.name = arg->name, .value = arg_res.type}); } auto tuple_e = Expression::MakeTupleLiteral(e->line_num, new_args); auto tuple_t = Value::MakeTupleValue(std::move(arg_types)); - return TCResult(tuple_e, tuple_t, new_types); + return TCExpression(tuple_e, tuple_t, new_types); } case ExpressionKind::FieldAccessExpression: { - auto res = TypeCheckExp(e->GetFieldAccessExpression().aggregate, types, - values, nullptr, TCContext::ValueContext); + auto res = + TypeCheckExp(e->GetFieldAccessExpression().aggregate, types, values); auto t = res.type; switch (t->tag()) { case ValKind::StructType: @@ -346,7 +290,7 @@ auto TypeCheckExp(const Expression* e, TypeEnv types, Env values, if (e->GetFieldAccessExpression().field == field.first) { const Expression* new_e = Expression::MakeFieldAccessExpression( e->line_num, res.exp, e->GetFieldAccessExpression().field); - return TCResult(new_e, field.second, res.types); + return TCExpression(new_e, field.second, res.types); } } // Search for a method @@ -354,7 +298,7 @@ auto TypeCheckExp(const Expression* e, TypeEnv types, Env values, if (e->GetFieldAccessExpression().field == method.first) { const Expression* new_e = Expression::MakeFieldAccessExpression( e->line_num, res.exp, e->GetFieldAccessExpression().field); - return TCResult(new_e, method.second, res.types); + return TCExpression(new_e, method.second, res.types); } } FATAL_COMPILATION_ERROR(e->line_num) @@ -366,7 +310,7 @@ auto TypeCheckExp(const Expression* e, TypeEnv types, Env values, if (e->GetFieldAccessExpression().field == field.name) { auto new_e = Expression::MakeFieldAccessExpression( e->line_num, res.exp, e->GetFieldAccessExpression().field); - return TCResult(new_e, field.value, res.types); + return TCExpression(new_e, field.value, res.types); } } FATAL_COMPILATION_ERROR(e->line_num) @@ -380,7 +324,7 @@ auto TypeCheckExp(const Expression* e, TypeEnv types, Env values, const Expression* new_e = Expression::MakeFieldAccessExpression( e->line_num, res.exp, e->GetFieldAccessExpression().field); auto fun_ty = Value::MakeFunctionType({}, vt->second, t); - return TCResult(new_e, fun_ty, res.types); + return TCExpression(new_e, fun_ty, res.types); } } FATAL_COMPILATION_ERROR(e->line_num) @@ -398,24 +342,23 @@ auto TypeCheckExp(const Expression* e, TypeEnv types, Env values, std::optional type = types.Get(e->GetIdentifierExpression().name); if (type) { - return TCResult(e, *type, types); + return TCExpression(e, *type, types); } else { FATAL_COMPILATION_ERROR(e->line_num) << "could not find `" << e->GetIdentifierExpression().name << "`"; } } case ExpressionKind::IntLiteral: - return TCResult(e, Value::MakeIntType(), types); + return TCExpression(e, Value::MakeIntType(), types); case ExpressionKind::BoolLiteral: - return TCResult(e, Value::MakeBoolType(), types); + return TCExpression(e, Value::MakeBoolType(), types); case ExpressionKind::PrimitiveOperatorExpression: { std::vector es; std::vector ts; auto new_types = types; for (const Expression* argument : e->GetPrimitiveOperatorExpression().arguments) { - auto res = TypeCheckExp(argument, types, values, nullptr, - TCContext::ValueContext); + auto res = TypeCheckExp(argument, types, values); new_types = res.types; es.push_back(res.exp); ts.push_back(res.type); @@ -425,55 +368,54 @@ auto TypeCheckExp(const Expression* e, TypeEnv types, Env values, switch (e->GetPrimitiveOperatorExpression().op) { case Operator::Neg: ExpectType(e->line_num, "negation", Value::MakeIntType(), ts[0]); - return TCResult(new_e, Value::MakeIntType(), new_types); + return TCExpression(new_e, Value::MakeIntType(), new_types); case Operator::Add: ExpectType(e->line_num, "addition(1)", Value::MakeIntType(), ts[0]); ExpectType(e->line_num, "addition(2)", Value::MakeIntType(), ts[1]); - return TCResult(new_e, Value::MakeIntType(), new_types); + return TCExpression(new_e, Value::MakeIntType(), new_types); case Operator::Sub: ExpectType(e->line_num, "subtraction(1)", Value::MakeIntType(), ts[0]); ExpectType(e->line_num, "subtraction(2)", Value::MakeIntType(), ts[1]); - return TCResult(new_e, Value::MakeIntType(), new_types); + return TCExpression(new_e, Value::MakeIntType(), new_types); case Operator::Mul: ExpectType(e->line_num, "multiplication(1)", Value::MakeIntType(), ts[0]); ExpectType(e->line_num, "multiplication(2)", Value::MakeIntType(), ts[1]); - return TCResult(new_e, Value::MakeIntType(), new_types); + return TCExpression(new_e, Value::MakeIntType(), new_types); case Operator::And: ExpectType(e->line_num, "&&(1)", Value::MakeBoolType(), ts[0]); ExpectType(e->line_num, "&&(2)", Value::MakeBoolType(), ts[1]); - return TCResult(new_e, Value::MakeBoolType(), new_types); + return TCExpression(new_e, Value::MakeBoolType(), new_types); case Operator::Or: ExpectType(e->line_num, "||(1)", Value::MakeBoolType(), ts[0]); ExpectType(e->line_num, "||(2)", Value::MakeBoolType(), ts[1]); - return TCResult(new_e, Value::MakeBoolType(), new_types); + return TCExpression(new_e, Value::MakeBoolType(), new_types); case Operator::Not: ExpectType(e->line_num, "!", Value::MakeBoolType(), ts[0]); - return TCResult(new_e, Value::MakeBoolType(), new_types); + return TCExpression(new_e, Value::MakeBoolType(), new_types); case Operator::Eq: ExpectType(e->line_num, "==", ts[0], ts[1]); - return TCResult(new_e, Value::MakeBoolType(), new_types); + return TCExpression(new_e, Value::MakeBoolType(), new_types); case Operator::Deref: ExpectPointerType(e->line_num, "*", ts[0]); - return TCResult(new_e, ts[0]->GetPointerType().type, new_types); + return TCExpression(new_e, ts[0]->GetPointerType().type, new_types); case Operator::Ptr: ExpectType(e->line_num, "*", Value::MakeTypeType(), ts[0]); - return TCResult(new_e, Value::MakeTypeType(), new_types); + return TCExpression(new_e, Value::MakeTypeType(), new_types); } break; } case ExpressionKind::CallExpression: { - auto fun_res = TypeCheckExp(e->GetCallExpression().function, types, - values, nullptr, TCContext::ValueContext); + auto fun_res = + TypeCheckExp(e->GetCallExpression().function, types, values); switch (fun_res.type->tag()) { case ValKind::FunctionType: { auto fun_t = fun_res.type; - auto arg_res = - TypeCheckExp(e->GetCallExpression().argument, fun_res.types, - values, fun_t->GetFunctionType().param, context); + auto arg_res = TypeCheckExp(e->GetCallExpression().argument, + fun_res.types, values); auto parameter_type = fun_t->GetFunctionType().param; auto return_type = fun_t->GetFunctionType().ret; if (fun_t->GetFunctionType().deduced.size() > 0) { @@ -497,7 +439,7 @@ auto TypeCheckExp(const Expression* e, TypeEnv types, Env values, } auto new_e = Expression::MakeCallExpression(e->line_num, fun_res.exp, arg_res.exp); - return TCResult(new_e, return_type, arg_res.types); + return TCExpression(new_e, return_type, arg_res.types); } default: { FATAL_COMPILATION_ERROR(e->line_num) @@ -508,48 +450,157 @@ auto TypeCheckExp(const Expression* e, TypeEnv types, Env values, break; } case ExpressionKind::FunctionTypeLiteral: { - switch (context) { - case TCContext::ValueContext: - case TCContext::TypeContext: { - auto pt = InterpExp(values, e->GetFunctionTypeLiteral().parameter); - auto rt = InterpExp(values, e->GetFunctionTypeLiteral().return_type); - auto new_e = Expression::MakeFunctionTypeLiteral( - e->line_num, ReifyType(pt, e->line_num), - ReifyType(rt, e->line_num)); - return TCResult(new_e, Value::MakeTypeType(), types); - } - case TCContext::PatternContext: { - auto param_res = TypeCheckExp(e->GetFunctionTypeLiteral().parameter, - types, values, nullptr, context); - auto ret_res = - TypeCheckExp(e->GetFunctionTypeLiteral().return_type, - param_res.types, values, nullptr, context); - auto new_e = Expression::MakeFunctionTypeLiteral( - e->line_num, ReifyType(param_res.type, e->line_num), - ReifyType(ret_res.type, e->line_num)); - return TCResult(new_e, Value::MakeTypeType(), ret_res.types); - } - } + auto pt = InterpExp(values, e->GetFunctionTypeLiteral().parameter); + auto rt = InterpExp(values, e->GetFunctionTypeLiteral().return_type); + auto new_e = Expression::MakeFunctionTypeLiteral( + e->line_num, ReifyType(pt, e->line_num), ReifyType(rt, e->line_num)); + return TCExpression(new_e, Value::MakeTypeType(), types); } case ExpressionKind::IntTypeLiteral: - return TCResult(e, Value::MakeTypeType(), types); + return TCExpression(e, Value::MakeTypeType(), types); case ExpressionKind::BoolTypeLiteral: - return TCResult(e, Value::MakeTypeType(), types); + return TCExpression(e, Value::MakeTypeType(), types); case ExpressionKind::TypeTypeLiteral: - return TCResult(e, Value::MakeTypeType(), types); - case ExpressionKind::AutoTypeLiteral: - return TCResult(e, Value::MakeTypeType(), types); + return TCExpression(e, Value::MakeTypeType(), types); case ExpressionKind::ContinuationTypeLiteral: - return TCResult(e, Value::MakeTypeType(), types); + return TCExpression(e, Value::MakeTypeType(), types); } } -auto TypecheckCase(const Value* expected, const Expression* pat, +// Equivalent to TypeCheckExp, but operates on Patterns instead of Expressions. +// `expected` is the type that this pattern is expected to have, if the +// surrounding context gives us that information. Otherwise, it is null. +auto TypeCheckPattern(const Pattern* p, TypeEnv types, Env values, + const Value* expected) -> TCPattern { + if (tracing_output) { + llvm::outs() << "checking pattern, "; + if (expected) { + llvm::outs() << "expecting " << *expected; + } + llvm::outs() << ", " << *p << "\n"; + } + switch (p->Tag()) { + case Pattern::Kind::AutoPattern: { + return {.pattern = p, .type = Value::MakeTypeType(), .types = types}; + } + case Pattern::Kind::BindingPattern: { + const auto& binding = cast(*p); + const Value* type; + switch (binding.Type()->Tag()) { + case Pattern::Kind::AutoPattern: { + if (expected == nullptr) { + FATAL_COMPILATION_ERROR(binding.LineNumber()) + << "auto not allowed here"; + } else { + type = expected; + } + break; + } + case Pattern::Kind::ExpressionPattern: { + type = InterpExp( + values, cast(binding.Type())->Expression()); + CHECK(type->tag() != ValKind::AutoType); + if (expected != nullptr) { + ExpectType(binding.LineNumber(), "pattern variable", type, + expected); + } + break; + } + case Pattern::Kind::TuplePattern: + case Pattern::Kind::BindingPattern: + case Pattern::Kind::AlternativePattern: + FATAL_COMPILATION_ERROR(binding.LineNumber()) + << "Unsupported type pattern"; + } + auto new_p = new BindingPattern( + binding.LineNumber(), binding.Name(), + new ExpressionPattern(ReifyType(type, binding.LineNumber()))); + if (binding.Name().has_value()) { + types.Set(*binding.Name(), type); + } + return {.pattern = new_p, .type = type, .types = types}; + } + case Pattern::Kind::TuplePattern: { + const auto& tuple = cast(*p); + std::vector new_fields; + std::vector field_types; + auto new_types = types; + if (expected && expected->tag() != ValKind::TupleValue) { + FATAL_COMPILATION_ERROR(p->LineNumber()) << "didn't expect a tuple"; + } + if (expected && + tuple.Fields().size() != expected->GetTupleValue().elements.size()) { + FATAL_COMPILATION_ERROR(tuple.LineNumber()) + << "tuples of different length"; + } + for (size_t i = 0; i < tuple.Fields().size(); ++i) { + const TuplePattern::Field& field = tuple.Fields()[i]; + const Value* expected_field_type = nullptr; + if (expected != nullptr) { + const TupleElement& expected_element = + expected->GetTupleValue().elements[i]; + if (expected_element.name != field.name) { + FATAL_COMPILATION_ERROR(tuple.LineNumber()) + << "field names do not match, expected " + << expected_element.name << " but got " << field.name; + } + expected_field_type = expected_element.value; + } + auto field_result = TypeCheckPattern(field.pattern, new_types, values, + expected_field_type); + new_types = field_result.types; + new_fields.push_back( + TuplePattern::Field(field.name, field_result.pattern)); + field_types.push_back({.name = field.name, .value = field_result.type}); + } + auto new_tuple = new TuplePattern(tuple.LineNumber(), new_fields); + auto tuple_t = Value::MakeTupleValue(std::move(field_types)); + return {.pattern = new_tuple, .type = tuple_t, .types = new_types}; + } + case Pattern::Kind::AlternativePattern: { + const auto& alternative = cast(*p); + const Value* choice_type = InterpExp(values, alternative.ChoiceType()); + if (choice_type->tag() != ValKind::ChoiceType) { + FATAL_COMPILATION_ERROR(alternative.LineNumber()) + << "alternative pattern does not name a choice type."; + } + if (expected != nullptr) { + ExpectType(alternative.LineNumber(), "alternative pattern", expected, + choice_type); + } + const Value* parameter_types = + FindInVarValues(alternative.AlternativeName(), + choice_type->GetChoiceType().alternatives); + if (parameter_types == nullptr) { + FATAL_COMPILATION_ERROR(alternative.LineNumber()) + << "'" << alternative.AlternativeName() + << "' is not an alternative of " << choice_type; + } + TCPattern arg_results = TypeCheckPattern(alternative.Arguments(), types, + values, parameter_types); + return {.pattern = new AlternativePattern( + alternative.LineNumber(), + ReifyType(choice_type, alternative.LineNumber()), + alternative.AlternativeName(), + cast(arg_results.pattern)), + .type = choice_type, + .types = arg_results.types}; + } + case Pattern::Kind::ExpressionPattern: { + TCExpression result = + TypeCheckExp(cast(p)->Expression(), types, values); + return {.pattern = new ExpressionPattern(result.exp), + .type = result.type, + .types = result.types}; + } + } +} + +auto TypecheckCase(const Value* expected, const Pattern* pat, const Statement* body, TypeEnv types, Env values, const Value*& ret_type) - -> std::pair { - auto pat_res = - TypeCheckExp(pat, types, values, expected, TCContext::PatternContext); + -> std::pair { + auto pat_res = TypeCheckPattern(pat, types, values, expected); auto res = TypeCheckStmt(body, pat_res.types, values, ret_type); return std::make_pair(pat, res.stmt); } @@ -568,11 +619,10 @@ auto TypeCheckStmt(const Statement* s, TypeEnv types, Env values, } switch (s->tag()) { case StatementKind::Match: { - auto res = TypeCheckExp(s->GetMatch().exp, types, values, nullptr, - TCContext::ValueContext); + auto res = TypeCheckExp(s->GetMatch().exp, types, values); auto res_type = res.type; auto new_clauses = - new std::list>(); + new std::list>(); for (auto& clause : *s->GetMatch().clauses) { new_clauses->push_back(TypecheckCase( res_type, clause.first, clause.second, types, values, ret_type)); @@ -582,8 +632,7 @@ auto TypeCheckStmt(const Statement* s, TypeEnv types, Env values, return TCStatement(new_s, types); } case StatementKind::While: { - auto cnd_res = TypeCheckExp(s->GetWhile().cond, types, values, nullptr, - TCContext::ValueContext); + auto cnd_res = TypeCheckExp(s->GetWhile().cond, types, values); ExpectType(s->line_num, "condition of `while`", Value::MakeBoolType(), cnd_res.type); auto body_res = @@ -602,11 +651,10 @@ auto TypeCheckStmt(const Statement* s, TypeEnv types, Env values, types); } case StatementKind::VariableDefinition: { - auto res = TypeCheckExp(s->GetVariableDefinition().init, types, values, - nullptr, TCContext::ValueContext); + auto res = TypeCheckExp(s->GetVariableDefinition().init, types, values); const Value* rhs_ty = res.type; - auto lhs_res = TypeCheckExp(s->GetVariableDefinition().pat, types, values, - rhs_ty, TCContext::PatternContext); + auto lhs_res = TypeCheckPattern(s->GetVariableDefinition().pat, types, + values, rhs_ty); const Statement* new_s = Statement::MakeVariableDefinition( s->line_num, s->GetVariableDefinition().pat, res.exp); return TCStatement(new_s, lhs_res.types); @@ -623,25 +671,21 @@ auto TypeCheckStmt(const Statement* s, TypeEnv types, Env values, types3); } case StatementKind::Assign: { - auto rhs_res = TypeCheckExp(s->GetAssign().rhs, types, values, nullptr, - TCContext::ValueContext); + auto rhs_res = TypeCheckExp(s->GetAssign().rhs, types, values); auto rhs_t = rhs_res.type; - auto lhs_res = TypeCheckExp(s->GetAssign().lhs, types, values, rhs_t, - TCContext::ValueContext); + auto lhs_res = TypeCheckExp(s->GetAssign().lhs, types, values); auto lhs_t = lhs_res.type; ExpectType(s->line_num, "assign", lhs_t, rhs_t); auto new_s = Statement::MakeAssign(s->line_num, lhs_res.exp, rhs_res.exp); return TCStatement(new_s, lhs_res.types); } case StatementKind::ExpressionStatement: { - auto res = TypeCheckExp(s->GetExpressionStatement().exp, types, values, - nullptr, TCContext::ValueContext); + auto res = TypeCheckExp(s->GetExpressionStatement().exp, types, values); auto new_s = Statement::MakeExpressionStatement(s->line_num, res.exp); return TCStatement(new_s, types); } case StatementKind::If: { - auto cnd_res = TypeCheckExp(s->GetIf().cond, types, values, nullptr, - TCContext::ValueContext); + auto cnd_res = TypeCheckExp(s->GetIf().cond, types, values); ExpectType(s->line_num, "condition of `if`", Value::MakeBoolType(), cnd_res.type); auto thn_res = @@ -653,8 +697,7 @@ auto TypeCheckStmt(const Statement* s, TypeEnv types, Env values, return TCStatement(new_s, types); } case StatementKind::Return: { - auto res = TypeCheckExp(s->GetReturn().exp, types, values, nullptr, - TCContext::ValueContext); + auto res = TypeCheckExp(s->GetReturn().exp, types, values); if (ret_type->tag() == ValKind::AutoType) { // The following infers the return type from the first 'return' // statement. This will get more difficult with subtyping, when we @@ -676,9 +719,8 @@ auto TypeCheckStmt(const Statement* s, TypeEnv types, Env values, return TCStatement(new_continuation, types); } case StatementKind::Run: { - TCResult argument_result = - TypeCheckExp(s->GetRun().argument, types, values, nullptr, - TCContext::ValueContext); + TCExpression argument_result = + TypeCheckExp(s->GetRun().argument, types, values); ExpectType(s->line_num, "argument of `run`", Value::MakeContinuationType(), argument_result.type); const Statement* new_run = @@ -706,7 +748,7 @@ auto CheckOrEnsureReturn(const Statement* stmt, bool void_return, int line_num) switch (stmt->tag()) { case StatementKind::Match: { auto new_clauses = - new std::list>(); + new std::list>(); for (auto i = stmt->GetMatch().clauses->begin(); i != stmt->GetMatch().clauses->end(); ++i) { auto s = CheckOrEnsureReturn(i->second, void_return, stmt->line_num); @@ -774,10 +816,9 @@ auto TypeCheckFunDef(const FunctionDefinition* f, TypeEnv types, Env values) values.Set(deduced.name, a); } // Type check the parameter pattern - auto param_res = TypeCheckExp(f->param_pattern, types, values, nullptr, - TCContext::PatternContext); + auto param_res = TypeCheckPattern(f->param_pattern, types, values, nullptr); // Evaluate the return type expression - auto return_type = InterpExp(values, f->return_type); + auto return_type = InterpPattern(values, f->return_type); if (f->name == "main") { ExpectType(f->line_num, "return type of `main`", Value::MakeIntType(), return_type); @@ -786,9 +827,9 @@ auto TypeCheckFunDef(const FunctionDefinition* f, TypeEnv types, Env values) auto res = TypeCheckStmt(f->body, param_res.types, values, return_type); bool void_return = TypeEqual(return_type, Value::MakeUnitTypeVal()); auto body = CheckOrEnsureReturn(res.stmt, void_return, f->line_num); - return new FunctionDefinition(f->line_num, f->name, f->deduced_parameters, - f->param_pattern, - ReifyType(return_type, f->line_num), body); + return new FunctionDefinition( + f->line_num, f->name, f->deduced_parameters, f->param_pattern, + new ExpressionPattern(ReifyType(return_type, f->line_num)), body); } auto TypeOfFunDef(TypeEnv types, Env values, const FunctionDefinition* fun_def) @@ -801,13 +842,13 @@ auto TypeOfFunDef(TypeEnv types, Env values, const FunctionDefinition* fun_def) values.Set(deduced.name, a); } // Type check the parameter pattern - auto param_res = TypeCheckExp(fun_def->param_pattern, types, values, nullptr, - TCContext::PatternContext); + auto param_res = + TypeCheckPattern(fun_def->param_pattern, types, values, nullptr); // Evaluate the return type expression - auto ret = InterpExp(values, fun_def->return_type); + auto ret = InterpPattern(values, fun_def->return_type); if (ret->tag() == ValKind::AutoType) { auto f = TypeCheckFunDef(fun_def, types, values); - ret = InterpExp(values, f->return_type); + ret = InterpPattern(values, f->return_type); } return Value::MakeFunctionType(fun_def->deduced_parameters, param_res.type, ret); @@ -819,10 +860,22 @@ auto TypeOfStructDef(const StructDefinition* sd, TypeEnv /*types*/, Env ct_top) VarValues methods; for (const Member* m : sd->members) { switch (m->tag()) { - case MemberKind::FieldMember: - auto t = InterpExp(ct_top, m->GetFieldMember().type); - fields.push_back(std::make_pair(m->GetFieldMember().name, t)); + case MemberKind::FieldMember: { + const BindingPattern* binding = m->GetFieldMember().binding; + if (!binding->Name().has_value()) { + FATAL_COMPILATION_ERROR(binding->LineNumber()) + << "Struct members must have names"; + } + const Expression* type_expression = + dyn_cast(binding->Type())->Expression(); + if (type_expression == nullptr) { + FATAL_COMPILATION_ERROR(binding->LineNumber()) + << "Struct members must have explicit types"; + } + auto type = InterpExp(ct_top, type_expression); + fields.push_back(std::make_pair(*binding->Name(), type)); break; + } } } return Value::MakeStructType(sd->name, std::move(fields), std::move(methods)); @@ -836,8 +889,14 @@ static auto GetName(const Declaration& d) -> const std::string& { return d.GetStructDeclaration().definition.name; case DeclarationKind::ChoiceDeclaration: return d.GetChoiceDeclaration().name; - case DeclarationKind::VariableDeclaration: - return d.GetVariableDeclaration().name; + case DeclarationKind::VariableDeclaration: { + const BindingPattern* binding = d.GetVariableDeclaration().binding; + if (!binding->Name().has_value()) { + FATAL_COMPILATION_ERROR(binding->LineNumber()) + << "Top-level variable declarations must have names"; + } + return *binding->Name(); + } } } @@ -872,9 +931,16 @@ auto MakeTypeChecked(const Declaration& d, const TypeEnv& types, // Signals a type error if the initializing expression does not have // the declared type of the variable, otherwise returns this // declaration with annotated types. - TCResult type_checked_initializer = TypeCheckExp( - var.initializer, types, values, nullptr, TCContext::ValueContext); - const Value* declared_type = InterpExp(values, var.type); + TCExpression type_checked_initializer = + TypeCheckExp(var.initializer, types, values); + const Expression* type = + dyn_cast(var.binding->Type())->Expression(); + if (type == nullptr) { + // TODO: consider adding support for `auto` + FATAL_COMPILATION_ERROR(var.source_location) + << "Type of a top-level variable must be an expression."; + } + const Value* declared_type = InterpExp(values, type); ExpectType(var.source_location, "initializer of variable", declared_type, type_checked_initializer.type); return d; @@ -926,8 +992,10 @@ static void TopLevel(const Declaration& d, TypeCheckContext* tops) { const auto& var = d.GetVariableDeclaration(); // Associate the variable name with it's declared type in the // compile-time symbol table. - const Value* declared_type = InterpExp(tops->values, var.type); - tops->types.Set(var.name, declared_type); + const Expression* type = + cast(var.binding->Type())->Expression(); + const Value* declared_type = InterpExp(tops->values, type); + tops->types.Set(*var.binding->Name(), declared_type); break; } } diff --git a/executable_semantics/interpreter/typecheck.h b/executable_semantics/interpreter/typecheck.h index 432089228cb6..afc7f77f6ee7 100644 --- a/executable_semantics/interpreter/typecheck.h +++ b/executable_semantics/interpreter/typecheck.h @@ -17,10 +17,8 @@ namespace Carbon { using TypeEnv = Dictionary; -enum class TCContext { ValueContext, PatternContext, TypeContext }; - -struct TCResult { - TCResult(const Expression* e, const Value* t, TypeEnv types) +struct TCExpression { + TCExpression(const Expression* e, const Value* t, TypeEnv types) : exp(e), type(t), types(types) {} const Expression* exp; @@ -28,6 +26,12 @@ struct TCResult { TypeEnv types; }; +struct TCPattern { + const Pattern* pattern; + const Value* type; + TypeEnv types; +}; + struct TCStatement { TCStatement(const Statement* s, TypeEnv types) : stmt(s), types(types) {} @@ -35,8 +39,10 @@ struct TCStatement { TypeEnv types; }; -auto TypeCheckExp(const Expression* e, TypeEnv types, Env values, - const Value* expected, TCContext context) -> TCResult; +auto TypeCheckExp(const Expression* e, TypeEnv types, Env values) + -> TCExpression; +auto TypeCheckPattern(const Pattern* p, TypeEnv types, Env values, + const Value* expected) -> TCPattern; auto TypeCheckStmt(const Statement*, TypeEnv, Env, Value const*&) -> TCStatement; diff --git a/executable_semantics/syntax/BUILD b/executable_semantics/syntax/BUILD index f8551220342f..62e7fe5109d8 100644 --- a/executable_semantics/syntax/BUILD +++ b/executable_semantics/syntax/BUILD @@ -42,23 +42,7 @@ cc_library( cc_library( name = "paren_contents", - srcs = ["paren_contents.cpp"], hdrs = ["paren_contents.h"], - deps = ["//executable_semantics/ast:expression"], -) - -cc_test( - name = "paren_contents_test", - srcs = ["paren_contents_test.cpp"], - env = { - # TODO(#580): Remove this when leaks are fixed. - "ASAN_OPTIONS": "detect_leaks=0", - }, - deps = [ - ":paren_contents", - "@llvm-project//llvm:gtest", - "@llvm-project//llvm:gtest_main", - ], ) genrule( diff --git a/executable_semantics/syntax/paren_contents.cpp b/executable_semantics/syntax/paren_contents.cpp deleted file mode 100644 index 9fb4c434f5ff..000000000000 --- a/executable_semantics/syntax/paren_contents.cpp +++ /dev/null @@ -1,22 +0,0 @@ -// 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 - -#include "executable_semantics/syntax/paren_contents.h" - -namespace Carbon { - -const Expression* ParenContents::AsExpression(int line_number) const { - if (fields_.size() == 1 && fields_.front().name == "" && - has_trailing_comma_ == HasTrailingComma::No) { - return new Expression(*fields_.front().expression); - } else { - return AsTuple(line_number); - } -} - -const Expression* ParenContents::AsTuple(int line_number) const { - return Expression::MakeTupleLiteral(line_number, fields_); -} - -} // namespace Carbon diff --git a/executable_semantics/syntax/paren_contents.h b/executable_semantics/syntax/paren_contents.h index 8fed4d1508a0..25cbf316b12c 100644 --- a/executable_semantics/syntax/paren_contents.h +++ b/executable_semantics/syntax/paren_contents.h @@ -5,47 +5,82 @@ #ifndef EXECUTABLE_SEMANTICS_SYNTAX_PAREN_CONTENTS_H_ #define EXECUTABLE_SEMANTICS_SYNTAX_PAREN_CONTENTS_H_ -#include +#include +#include +#include -#include "executable_semantics/ast/expression.h" +#include "executable_semantics/common/error.h" namespace Carbon { -// Represents the syntactic contents of an expression delimited by -// parentheses. Such expressions can be interpreted as either tuples or -// arbitrary expressions, depending on their context and the syntax of their +// Represents the syntactic contents of an expression or pattern delimited by +// parentheses. In those syntaxes, parentheses can be used either for grouping +// or for forming a tuple, depending on their context and the syntax of their // contents; this class helps calling code resolve that ambiguity. Since that // ambiguity is purely syntactic, this class should only be needed during // parsing. -class ParenContents { - public: - // Indicates whether the paren expression's contents end with a comma. - enum class HasTrailingComma { Yes, No }; +// +// `Term` is the type of the syntactic grouping being built, and the type of +// the individual syntactic units it's built from; typically it should be +// either `Expression` or `Pattern`. +template +struct ParenContents { + struct Element { + std::optional name; + const Term* term; + }; - // Constructs a ParenContents representing the contents of "()". - ParenContents() : fields_({}), has_trailing_comma_(HasTrailingComma::No) {} + // If this object represents a single term, with no name and no trailing + // comma, this method returns that term. This typically means the parentheses + // can be interpreted as grouping. + auto SingleTerm() const -> std::optional; - // Constructs a ParenContents representing the given list of fields, - // with or without a trailing comma. - ParenContents(std::vector fields, - HasTrailingComma has_trailing_comma) - : fields_(fields), has_trailing_comma_(has_trailing_comma) {} + // Converts `elements` to std::vector. TupleElement must + // have a constructor that takes a std::string and a const Term*. + // + // TODO: Find a way to deduce TupleElement from Term. + template + auto TupleElements(int line_num) const -> std::vector; - ParenContents(const ParenContents&) = default; - ParenContents& operator=(const ParenContents&) = default; - - // Returns the paren expression, interpreted as a tuple. - const Expression* AsTuple(int line_number) const; - - // Returns the paren expression, with no external constraints on what kind - // of expression it represents. - const Expression* AsExpression(int line_number) const; - - private: - std::vector fields_; - HasTrailingComma has_trailing_comma_; + std::vector elements; + bool has_trailing_comma; }; +// Implementation details only below here. + +template +auto ParenContents::SingleTerm() const -> std::optional { + if (elements.size() == 1 && !elements.front().name.has_value() && + !has_trailing_comma) { + return elements.front().term; + } else { + return std::nullopt; + } +} + +template +template +auto ParenContents::TupleElements(int line_num) const + -> std::vector { + std::vector result; + int i = 0; + bool seen_named_member = false; + for (auto element : elements) { + if (element.name.has_value()) { + seen_named_member = true; + result.push_back(TupleElement(*element.name, element.term)); + } else { + if (seen_named_member) { + FATAL_USER_ERROR(line_num) + << "positional members must come before named members"; + } + result.push_back(TupleElement(std::to_string(i), element.term)); + } + ++i; + } + return result; +} + } // namespace Carbon #endif // EXECUTABLE_SEMANTICS_SYNTAX_PAREN_CONTENTS_H_ diff --git a/executable_semantics/syntax/paren_contents_test.cpp b/executable_semantics/syntax/paren_contents_test.cpp deleted file mode 100644 index db6bf61b242c..000000000000 --- a/executable_semantics/syntax/paren_contents_test.cpp +++ /dev/null @@ -1,114 +0,0 @@ -// 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 - -#include "executable_semantics/syntax/paren_contents.h" - -#include "gtest/gtest.h" - -namespace Carbon { -namespace { - -TEST(ParenContentsTest, EmptyAsExpression) { - ParenContents contents; - const Expression* expression = contents.AsExpression(/*line_num=*/1); - EXPECT_EQ(expression->line_num, 1); - ASSERT_EQ(expression->tag(), ExpressionKind::TupleLiteral); - EXPECT_EQ(expression->GetTupleLiteral().fields.size(), 0); -} - -TEST(ParenContentsTest, EmptyAsTuple) { - ParenContents contents; - const Expression* tuple = contents.AsTuple(/*line_num=*/1); - EXPECT_EQ(tuple->line_num, 1); - ASSERT_EQ(tuple->tag(), ExpressionKind::TupleLiteral); - EXPECT_EQ(tuple->GetTupleLiteral().fields.size(), 0); -} - -TEST(ParenContentsTest, UnaryNoCommaAsExpression) { - // Equivalent to a code fragment like - // ``` - // ( - // 42 - // ) - // ``` - ParenContents contents( - {{.expression = Expression::MakeIntLiteral(/*line_num=*/2, 42)}}, - ParenContents::HasTrailingComma::No); - - const Expression* expression = contents.AsExpression(/*line_num=*/1); - EXPECT_EQ(expression->line_num, 2); - ASSERT_EQ(expression->tag(), ExpressionKind::IntLiteral); -} - -TEST(ParenContentsTest, UnaryNoCommaAsTuple) { - ParenContents contents( - {{.expression = Expression::MakeIntLiteral(/*line_num=*/2, 42)}}, - ParenContents::HasTrailingComma::No); - - const Expression* tuple = contents.AsTuple(/*line_num=*/1); - EXPECT_EQ(tuple->line_num, 1); - ASSERT_EQ(tuple->tag(), ExpressionKind::TupleLiteral); - std::vector fields = tuple->GetTupleLiteral().fields; - ASSERT_EQ(fields.size(), 1); - EXPECT_EQ(fields[0].expression->tag(), ExpressionKind::IntLiteral); -} - -TEST(ParenContentsTest, UnaryWithCommaAsExpression) { - ParenContents contents( - {{.expression = Expression::MakeIntLiteral(/*line_num=*/2, 42)}}, - ParenContents::HasTrailingComma::Yes); - - const Expression* expression = contents.AsExpression(/*line_num=*/1); - EXPECT_EQ(expression->line_num, 1); - ASSERT_EQ(expression->tag(), ExpressionKind::TupleLiteral); - std::vector fields = expression->GetTupleLiteral().fields; - ASSERT_EQ(fields.size(), 1); - EXPECT_EQ(fields[0].expression->tag(), ExpressionKind::IntLiteral); -} - -TEST(ParenContentsTest, UnaryWithCommaAsTuple) { - ParenContents contents( - {{.expression = Expression::MakeIntLiteral(/*line_num=*/2, 42)}}, - ParenContents::HasTrailingComma::Yes); - - const Expression* tuple = contents.AsTuple(/*line_num=*/1); - EXPECT_EQ(tuple->line_num, 1); - ASSERT_EQ(tuple->tag(), ExpressionKind::TupleLiteral); - std::vector fields = tuple->GetTupleLiteral().fields; - ASSERT_EQ(fields.size(), 1); - EXPECT_EQ(fields[0].expression->tag(), ExpressionKind::IntLiteral); -} - -TEST(ParenContentsTest, BinaryAsExpression) { - ParenContents contents( - {{.expression = Expression::MakeIntLiteral(/*line_num=*/2, 42)}, - {.expression = Expression::MakeIntLiteral(/*line_num=*/3, 42)}}, - ParenContents::HasTrailingComma::Yes); - - const Expression* expression = contents.AsExpression(/*line_num=*/1); - EXPECT_EQ(expression->line_num, 1); - ASSERT_EQ(expression->tag(), ExpressionKind::TupleLiteral); - std::vector fields = expression->GetTupleLiteral().fields; - ASSERT_EQ(fields.size(), 2); - EXPECT_EQ(fields[0].expression->tag(), ExpressionKind::IntLiteral); - EXPECT_EQ(fields[1].expression->tag(), ExpressionKind::IntLiteral); -} - -TEST(ParenContentsTest, BinaryAsTuple) { - ParenContents contents( - {{.expression = Expression::MakeIntLiteral(/*line_num=*/2, 42)}, - {.expression = Expression::MakeIntLiteral(/*line_num=*/3, 42)}}, - ParenContents::HasTrailingComma::Yes); - - const Expression* tuple = contents.AsTuple(/*line_num=*/1); - EXPECT_EQ(tuple->line_num, 1); - ASSERT_EQ(tuple->tag(), ExpressionKind::TupleLiteral); - std::vector fields = tuple->GetTupleLiteral().fields; - ASSERT_EQ(fields.size(), 2); - EXPECT_EQ(fields[0].expression->tag(), ExpressionKind::IntLiteral); - EXPECT_EQ(fields[1].expression->tag(), ExpressionKind::IntLiteral); -} - -} // namespace -} // namespace Carbon diff --git a/executable_semantics/syntax/parser.ypp b/executable_semantics/syntax/parser.ypp index a4c035bc5512..23482c0ef403 100644 --- a/executable_semantics/syntax/parser.ypp +++ b/executable_semantics/syntax/parser.ypp @@ -49,6 +49,7 @@ #include #include #include +#include #include "executable_semantics/syntax/syntax_helpers.h" #include "executable_semantics/syntax/parse_and_lex_context.h" @@ -59,7 +60,9 @@ #include "executable_semantics/ast/abstract_syntax_tree.h" #include "executable_semantics/ast/declaration.h" +#include "executable_semantics/ast/expression.h" #include "executable_semantics/ast/function_definition.h" +#include "executable_semantics/ast/pattern.h" #include "executable_semantics/syntax/paren_contents.h" namespace Carbon { @@ -94,21 +97,28 @@ void yy::parser::error(const location_type&, const std::string& message) { %type generic_binding %type > deduced_params %type > deduced_param_list -%type pattern +%type pattern +%type non_expression_pattern %type return_type %type paren_expression %type tuple %type > binding_lhs -%type variable_declaration +%type variable_declaration %type member %type > member_list -%type field_initializer -%type paren_contents -%type > paren_contents_without_trailing_comma +%type ::Element> paren_expression_element +%type > paren_expression_base +%type > paren_expression_contents +%type paren_pattern +%type tuple_pattern +%type maybe_empty_tuple_pattern +%type > paren_pattern_base +%type ::Element> paren_pattern_element +%type > paren_pattern_contents %type > alternative %type >> alternative_list -%type *> clause -%type >*> clause_list +%type *> clause +%type >*> clause_list %token END_OF_FILE 0 %token AND %token OR @@ -192,14 +202,6 @@ void yy::parser::error(const location_type&, const std::string& message) { input: declaration_list { parsed_program = $1; } ; -pattern: - expression - { $$ = $1; } -; -binding_lhs: - identifier { $$ = $1; } -| UNDERSCORE { $$ = std::nullopt; } -; expression: identifier { $$ = Carbon::Expression::MakeIdentifierExpression(yylineno, $1); } @@ -207,10 +209,6 @@ expression: { $$ = Carbon::Expression::MakeFieldAccessExpression(yylineno, $1, $2); } | expression "[" expression "]" { $$ = Carbon::Expression::MakeIndexExpression(yylineno, $1, $3); } -| binding_lhs ":" expression - { - $$ = Carbon::Expression::MakeBindingExpression(yylineno, $1, $3); - } | integer_literal { $$ = Carbon::Expression::MakeIntLiteral(yylineno, $1); } | TRUE @@ -223,8 +221,6 @@ expression: { $$ = Carbon::Expression::MakeBoolTypeLiteral(yylineno); } | TYPE { $$ = Carbon::Expression::MakeTypeTypeLiteral(yylineno); } -| AUTO - { $$ = Carbon::Expression::MakeAutoTypeLiteral(yylineno); } | CONTINUATION_TYPE { $$ = Carbon::Expression::MakeContinuationTypeLiteral(yylineno); } | paren_expression { $$ = $1; } @@ -271,55 +267,133 @@ expression: ; designator: "." identifier { $$ = $2; } ; -paren_expression: "(" paren_contents ")" - { $$ = $2.AsExpression(yylineno); } +paren_expression: paren_expression_base + { $$ = Carbon::ExpressionFromParenContents(yylineno, $1); } ; -tuple: "(" paren_contents ")" - { $$ = $2.AsTuple(yylineno); } +tuple: paren_expression_base + { $$ = Carbon::TupleExpressionFromParenContents(yylineno, $1); } ; -field_initializer: - pattern - { $$ = Carbon::FieldInitializer({"", $1}); } -| designator "=" pattern - { $$ = Carbon::FieldInitializer({$1, $3}); } +paren_expression_element: + expression + { $$ = {.name = std::nullopt, .term = $1}; } +| designator "=" expression + { $$ = {.name = $1, .term = $3}; } ; -paren_contents: - // Empty - { $$ = Carbon::ParenContents(); } -| paren_contents_without_trailing_comma +paren_expression_base: + "(" ")" + { $$ = {.elements = {}, .has_trailing_comma = false}; } +| "(" paren_expression_contents ")" + { $$ = $2; } +| "(" paren_expression_contents "," ")" { - $$ = Carbon::ParenContents($1, - Carbon::ParenContents::HasTrailingComma::No); - } -| paren_contents_without_trailing_comma "," - { - $$ = Carbon::ParenContents($1, - Carbon::ParenContents::HasTrailingComma::Yes); + $$ = $2; + $$.has_trailing_comma = true; } ; -paren_contents_without_trailing_comma: - field_initializer - { $$ = {$1}; } -| paren_contents_without_trailing_comma "," field_initializer +paren_expression_contents: + paren_expression_element + { $$ = {.elements = {$1}, .has_trailing_comma = false}; } +| paren_expression_contents "," paren_expression_element { $$ = $1; - $$.push_back($3); + $$.elements.push_back($3); } ; + +// In many cases, using `pattern` recursively will result in ambiguities. +// When that happens, it's necessary to factor out two separate productions, +// one for when the sub-pattern is an expression, and one for when it is not. +// To facilitate this, non-terminals besides `pattern` whose names contain +// `pattern` are structured to be disjoint from `expression`, unless otherwise +// specified. +pattern: + non_expression_pattern + { $$ = $1; } +| expression + { $$ = new Carbon::ExpressionPattern($1); } +; +non_expression_pattern: + AUTO + { $$ = new Carbon::AutoPattern(yylineno); } +| binding_lhs ":" pattern + { $$ = new Carbon::BindingPattern(yylineno, $1, $3); } +| paren_pattern + { $$ = $1; } +| expression tuple_pattern + { $$ = new Carbon::AlternativePattern(yylineno, $1, $2); } +; +binding_lhs: + identifier { $$ = $1; } +| UNDERSCORE { $$ = std::nullopt; } +; +paren_pattern: paren_pattern_base + { $$ = Carbon::PatternFromParenContents(yylineno, $1); } +; +paren_pattern_base: + "(" paren_pattern_contents ")" + { $$ = $2; } +| "(" paren_pattern_contents "," ")" + { + $$ = $2; + $$.has_trailing_comma = true; + } +; +// paren_pattern is analogous to paren_expression, but in order to avoid +// ambiguities, it must be disjoint from paren_expression, meaning it must +// contain at least one non_expression_pattern. The structure of this rule +// is very different from the corresponding expression rule because is has to +// enforce that requirement. +paren_pattern_contents: + paren_pattern_element + { $$ = {.elements = {$1}, .has_trailing_comma = false }; } +| paren_expression_contents "," paren_pattern_element + { + $$ = Carbon::ParenExpressionToParenPattern($1); + $$.elements.push_back($3); + } +| paren_pattern_contents "," paren_expression_element + { + $$ = $1; + $$.elements.push_back({.name = $3.name, .term = new Carbon::ExpressionPattern($3.term)}); + } +| paren_pattern_contents "," paren_pattern_element + { + $$ = $1; + $$.elements.push_back($3); + } +; +paren_pattern_element: + non_expression_pattern + { $$ = {.name = std::nullopt, .term = $1}; } +| designator "=" non_expression_pattern + { $$ = {.name = $1, .term = $3}; } +; +tuple_pattern: paren_pattern_base + { $$ = Carbon::TuplePatternFromParenContents(yylineno, $1); } +; +// Unlike most `pattern` nonterminals, this one overlaps with `expression`, +// so it should be used only when prior context (such as an introducer) +// rules out the possibility of an `expression` at this point. +maybe_empty_tuple_pattern: + "(" ")" + { $$ = new Carbon::TuplePattern(yylineno, {}); } +| tuple_pattern + { $$ = $1; } +; clause: CASE pattern DBLARROW statement - { $$ = new std::pair($2, $4); } + { $$ = new std::pair($2, $4); } | DEFAULT DBLARROW statement { - auto vp = Carbon::Expression::MakeBindingExpression( - yylineno, "_", Carbon::Expression::MakeAutoTypeLiteral(yylineno)); - $$ = new std::pair(vp, $3); + auto vp = new Carbon::BindingPattern( + yylineno, std::nullopt, new Carbon::AutoPattern(yylineno)); + $$ = new std::pair(vp, $3); } ; clause_list: // Empty { - $$ = new std::list>(); + $$ = new std::list>(); } | clause clause_list { $$ = $2; $$->push_front(*$1); } @@ -407,25 +481,30 @@ deduced_params: { $$ = $2; } ; function_definition: - FN identifier deduced_params tuple return_type block - { $$ = Carbon::FunctionDefinition(yylineno, $2, $3, $4, $5, $6); } -| FN identifier deduced_params tuple DBLARROW expression ";" + FN identifier deduced_params maybe_empty_tuple_pattern return_type block + { + $$ = Carbon::FunctionDefinition( + yylineno, $2, $3, $4, new Carbon::ExpressionPattern($5), $6); + } +| FN identifier deduced_params maybe_empty_tuple_pattern DBLARROW expression ";" { $$ = Carbon::FunctionDefinition( yylineno, $2, $3, $4, - Carbon::Expression::MakeAutoTypeLiteral(yylineno), + new Carbon::AutoPattern(yylineno), Carbon::Statement::MakeReturn(yylineno, $6)); } ; function_declaration: - FN identifier deduced_params tuple return_type ";" - { $$ = Carbon::FunctionDefinition(yylineno, $2, $3, $4, $5, 0); } + FN identifier deduced_params maybe_empty_tuple_pattern return_type ";" + { + $$ = Carbon::FunctionDefinition( + yylineno, $2, $3, $4, new Carbon::ExpressionPattern($5), 0); } ; -variable_declaration: identifier ":" expression - { $$ = Carbon::Member::MakeFieldMember(yylineno, $1, $3); } +variable_declaration: identifier ":" pattern + { $$ = new Carbon::BindingPattern(yylineno, $1, $3); } ; member: VAR variable_declaration ";" - { $$ = $2; } + { $$ = Carbon::Member::MakeFieldMember(yylineno, $2); } ; member_list: // Empty @@ -468,8 +547,7 @@ declaration: } | VAR variable_declaration "=" expression ";" { - $$ = Carbon::Declaration::MakeVariableDeclaration( - yylineno, $2->GetFieldMember().name, $2->GetFieldMember().type, $4); + $$ = Carbon::Declaration::MakeVariableDeclaration(yylineno, $2, $4); } ; declaration_list: diff --git a/executable_semantics/test_list.bzl b/executable_semantics/test_list.bzl index 800056a4d71a..a3e8a5eab8c4 100644 --- a/executable_semantics/test_list.bzl +++ b/executable_semantics/test_list.bzl @@ -60,7 +60,6 @@ TEST_LIST = [ "match_int", "match_int_default", "match_placeholder", - "match_type", "next", "no_match", "pattern_init", diff --git a/executable_semantics/testdata/fun6_fail_type.golden b/executable_semantics/testdata/fun6_fail_type.golden index 5e8611bcad73..0bfd7543f597 100644 --- a/executable_semantics/testdata/fun6_fail_type.golden +++ b/executable_semantics/testdata/fun6_fail_type.golden @@ -1,2 +1,4 @@ -COMPILATION ERROR: 10: tuples of different length +COMPILATION ERROR: 10: type error in call +expected: (0 = Int, 1 = Int) +actual: (0 = (0 = Int, 1 = Int)) EXIT CODE: 255 diff --git a/executable_semantics/testdata/match_type.carbon b/executable_semantics/testdata/match_type.carbon deleted file mode 100644 index 6f05f65669b1..000000000000 --- a/executable_semantics/testdata/match_type.carbon +++ /dev/null @@ -1,17 +0,0 @@ -// 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 - -fn main() -> Int { - var t: auto = fnty (Int,Int); - var x: Int = 0; - match (t) { - case z: Type => - x = x + 1; - } - match (t) { - case fnty(a: Type,b: Type) => - x = x - 1; - } - return x; -} diff --git a/executable_semantics/testdata/match_type.golden b/executable_semantics/testdata/match_type.golden deleted file mode 100644 index 2db2755da426..000000000000 --- a/executable_semantics/testdata/match_type.golden +++ /dev/null @@ -1 +0,0 @@ -result: 0 diff --git a/executable_semantics/testdata/pattern_variable_fail.golden b/executable_semantics/testdata/pattern_variable_fail.golden index 5e1c441aa198..aff99a11b2ed 100644 --- a/executable_semantics/testdata/pattern_variable_fail.golden +++ b/executable_semantics/testdata/pattern_variable_fail.golden @@ -1,2 +1,2 @@ -COMPILATION ERROR: 7: pattern variables are only allowed in pattern context +executable_semantics/testdata/pattern_variable_fail.carbon:7: syntax error, unexpected : EXIT CODE: 255