diff --git a/executable_semantics/ast/BUILD b/executable_semantics/ast/BUILD index 05734ff50fd3..7f85ea2d5c6b 100644 --- a/executable_semantics/ast/BUILD +++ b/executable_semantics/ast/BUILD @@ -77,8 +77,8 @@ cc_library( ], deps = [ ":ast_node", - ":member", ":pattern", + ":return_term", ":source_location", ":statement", ":static_scope", @@ -89,6 +89,18 @@ cc_library( ], ) +cc_library( + name = "return_term", + hdrs = ["return_term.h"], + deps = [ + ":source_location", + "//common:check", + "//common:ostream", + "//executable_semantics/common:nonnull", + "@llvm-project//llvm:Support", + ], +) + cc_library( name = "expression", srcs = ["expression.cpp"], @@ -119,20 +131,6 @@ cc_test( ], ) -cc_library( - name = "member", - srcs = ["member.cpp"], - hdrs = ["member.h"], - deps = [ - ":expression", - ":pattern", - ":source_location", - "//common:ostream", - "//executable_semantics/common:arena", - "@llvm-project//llvm:Support", - ], -) - cc_library( name = "library_name", hdrs = ["library_name.h"], @@ -186,7 +184,6 @@ cc_library( ":source_location", ":value_category", "//common:check", - "//executable_semantics/common:arena", "//executable_semantics/common:error", "//executable_semantics/common:nonnull", ], @@ -209,6 +206,7 @@ cc_library( ":ast_node", ":expression", ":pattern", + ":return_term", ":source_location", ":static_scope", ":value_category", diff --git a/executable_semantics/ast/ast_rtti.txt b/executable_semantics/ast/ast_rtti.txt index 4782f766cc26..aafaa941901c 100644 --- a/executable_semantics/ast/ast_rtti.txt +++ b/executable_semantics/ast/ast_rtti.txt @@ -50,5 +50,3 @@ abstract class Expression : AstNode; class IdentifierExpression : Expression; class IntrinsicExpression : Expression; class UnimplementedExpression : Expression; -abstract class Member : AstNode; - class FieldMember : Member; diff --git a/executable_semantics/ast/ast_test_matchers_test.cpp b/executable_semantics/ast/ast_test_matchers_test.cpp index 3066e90af828..b4c4a2d097ce 100644 --- a/executable_semantics/ast/ast_test_matchers_test.cpp +++ b/executable_semantics/ast/ast_test_matchers_test.cpp @@ -104,7 +104,7 @@ TEST(MatchesReturnTest, BasicUsage) { TEST(MatchesFunctionDeclarationTest, BasicUsage) { TuplePattern params(DummyLoc, {}); Block body(DummyLoc, {}); - FunctionDeclaration decl(DummyLoc, "Foo", {}, ¶ms, + FunctionDeclaration decl(DummyLoc, "Foo", {}, std::nullopt, ¶ms, ReturnTerm::Omitted(DummyLoc), &body); EXPECT_THAT(decl, MatchesFunctionDeclaration()); @@ -117,7 +117,7 @@ TEST(MatchesFunctionDeclarationTest, BasicUsage) { EXPECT_THAT(decl, Not(MatchesFunctionDeclaration().WithBody(MatchesLiteral(0)))); - FunctionDeclaration forward_decl(DummyLoc, "Foo", {}, ¶ms, + FunctionDeclaration forward_decl(DummyLoc, "Foo", {}, std::nullopt, ¶ms, ReturnTerm::Omitted(DummyLoc), std::nullopt); EXPECT_THAT(forward_decl, MatchesFunctionDeclaration().WithName("Foo")); EXPECT_THAT(forward_decl, Not(MatchesFunctionDeclaration().WithBody(_))); @@ -150,7 +150,7 @@ TEST(MatchesUnimplementedExpressionTest, BasicUsage) { TEST(ASTDeclarationsTest, BasicUsage) { TuplePattern params(DummyLoc, {}); Block body(DummyLoc, {}); - FunctionDeclaration decl(DummyLoc, "Foo", {}, ¶ms, + FunctionDeclaration decl(DummyLoc, "Foo", {}, std::nullopt, ¶ms, ReturnTerm::Omitted(DummyLoc), &body); AST ast = {.declarations = {&decl}}; diff --git a/executable_semantics/ast/declaration.cpp b/executable_semantics/ast/declaration.cpp index 429d8718f057..0158e9a89590 100644 --- a/executable_semantics/ast/declaration.cpp +++ b/executable_semantics/ast/declaration.cpp @@ -22,7 +22,7 @@ void Declaration::Print(llvm::raw_ostream& out) const { case DeclarationKind::ClassDeclaration: { const auto& class_decl = cast(*this); out << "class " << class_decl.name() << " {\n"; - for (Nonnull m : class_decl.members()) { + for (Nonnull m : class_decl.members()) { out << *m; } out << "}\n"; @@ -41,7 +41,11 @@ void Declaration::Print(llvm::raw_ostream& out) const { case DeclarationKind::VariableDeclaration: { const auto& var = cast(*this); - out << "var " << var.binding() << " = " << var.initializer() << "\n"; + out << "var " << var.binding(); + if (var.has_initializer()) { + out << " = " << var.initializer(); + } + out << ";\n"; break; } } @@ -64,6 +68,31 @@ void ReturnTerm::Print(llvm::raw_ostream& out) const { } } +// Look for the `me` parameter in the `deduced_parameters_` +// and put it in the `me_pattern_`. +void FunctionDeclaration::ResolveDeducedAndReceiver( + const std::vector>& deduced_params) { + for (Nonnull param : deduced_params) { + switch (param->kind()) { + case AstNodeKind::GenericBinding: + deduced_parameters_.push_back(&cast(*param)); + break; + case AstNodeKind::BindingPattern: { + Nonnull bp = &cast(*param); + if (me_pattern_.has_value() || bp->name() != "me") { + FATAL_COMPILATION_ERROR(source_loc()) + << "illegal binding pattern in implicit parameter list"; + } + me_pattern_ = bp; + break; + } + default: + FATAL_COMPILATION_ERROR(source_loc()) + << "illegal AST node in implicit parameter list"; + } + } +} + void FunctionDeclaration::PrintDepth(int depth, llvm::raw_ostream& out) const { out << "fn " << name_ << " "; if (!deduced_parameters_.empty()) { diff --git a/executable_semantics/ast/declaration.h b/executable_semantics/ast/declaration.h index 11263dd21444..fd1f060aff3d 100644 --- a/executable_semantics/ast/declaration.h +++ b/executable_semantics/ast/declaration.h @@ -10,8 +10,9 @@ #include #include "common/ostream.h" -#include "executable_semantics/ast/member.h" +#include "executable_semantics/ast/ast_node.h" #include "executable_semantics/ast/pattern.h" +#include "executable_semantics/ast/return_term.h" #include "executable_semantics/ast/source_location.h" #include "executable_semantics/ast/statement.h" #include "executable_semantics/ast/static_scope.h" @@ -34,8 +35,8 @@ class Declaration : public AstNode { public: ~Declaration() override = 0; - Declaration(const Member&) = delete; - auto operator=(const Member&) -> Declaration& = delete; + Declaration(const Declaration&) = delete; + auto operator=(const Declaration&) -> Declaration& = delete; void Print(llvm::raw_ostream& out) const override; @@ -126,100 +127,24 @@ class GenericBinding : public AstNode { std::optional> constant_value_; }; -// The syntactic representation of a function declaration's return type. -// This syntax can take one of three forms: -// - An _explicit_ term consists of `->` followed by a type expression. -// - An _auto_ term consists of `-> auto`. -// - An _omitted_ term consists of no tokens at all. -// Each of these forms has a corresponding factory function. -class ReturnTerm { - public: - ReturnTerm(const ReturnTerm&) = default; - auto operator=(const ReturnTerm&) -> ReturnTerm& = default; - - // Represents an omitted return term at `source_loc`. - static auto Omitted(SourceLocation source_loc) -> ReturnTerm { - return ReturnTerm(ReturnKind::Omitted, source_loc); - } - - // Represents an auto return term at `source_loc`. - static auto Auto(SourceLocation source_loc) -> ReturnTerm { - return ReturnTerm(ReturnKind::Auto, source_loc); - } - - // Represents an explicit return term with the given type expression. - static auto Explicit(Nonnull type_expression) -> ReturnTerm { - return ReturnTerm(type_expression); - } - - // Returns true if this represents an omitted return term. - auto is_omitted() const -> bool { return kind_ == ReturnKind::Omitted; } - - // Returns true if this represents an auto return term. - auto is_auto() const -> bool { return kind_ == ReturnKind::Auto; } - - // If this represents an explicit return term, returns the type expression. - // Otherwise, returns nullopt. - auto type_expression() const -> std::optional> { - return type_expression_; - } - auto type_expression() -> std::optional> { - return type_expression_; - } - - // The static return type this term resolves to. Cannot be called before - // typechecking. - auto static_type() const -> const Value& { return **static_type_; } - - // Sets the value of static_type(). Can only be called once, during - // typechecking. - void set_static_type(Nonnull type) { static_type_ = type; } - - // Returns whether static_type() has been set. Should only be called - // during typechecking: before typechecking it's guaranteed to be false, - // and after typechecking it's guaranteed to be true. - auto has_static_type() const -> bool { return static_type_.has_value(); } - - auto source_loc() const -> SourceLocation { return source_loc_; } - - void Print(llvm::raw_ostream& out) const; - LLVM_DUMP_METHOD void Dump() const { Print(llvm::errs()); } - - private: - enum class ReturnKind { Omitted, Auto, Expression }; - - explicit ReturnTerm(ReturnKind kind, SourceLocation source_loc) - : kind_(kind), source_loc_(source_loc) { - CHECK(kind != ReturnKind::Expression); - } - - explicit ReturnTerm(Nonnull type_expression) - : kind_(ReturnKind::Expression), - type_expression_(type_expression), - source_loc_(type_expression->source_loc()) {} - - ReturnKind kind_; - std::optional> type_expression_; - std::optional> static_type_; - - SourceLocation source_loc_; -}; - class FunctionDeclaration : public Declaration { public: using ImplementsCarbonNamedEntity = void; FunctionDeclaration(SourceLocation source_loc, std::string name, - std::vector> deduced_params, + std::vector> deduced_params, + std::optional> me_pattern, Nonnull param_pattern, ReturnTerm return_term, std::optional> body) : Declaration(AstNodeKind::FunctionDeclaration, source_loc), name_(std::move(name)), - deduced_parameters_(std::move(deduced_params)), + me_pattern_(me_pattern), param_pattern_(param_pattern), return_term_(return_term), - body_(body) {} + body_(body) { + ResolveDeducedAndReceiver(deduced_params); + } static auto classof(const AstNode* node) -> bool { return InheritsFromFunctionDeclaration(node->kind()); @@ -235,6 +160,8 @@ class FunctionDeclaration : public Declaration { auto deduced_parameters() -> llvm::ArrayRef> { return deduced_parameters_; } + auto me_pattern() const -> const BindingPattern& { return **me_pattern_; } + auto me_pattern() -> BindingPattern& { return **me_pattern_; } auto param_pattern() const -> const TuplePattern& { return *param_pattern_; } auto param_pattern() -> TuplePattern& { return *param_pattern_; } auto return_term() const -> const ReturnTerm& { return return_term_; } @@ -254,9 +181,13 @@ class FunctionDeclaration : public Declaration { constant_value_ = value; } + bool is_method() const { return me_pattern_.has_value(); } + private: + void ResolveDeducedAndReceiver(const std::vector>&); std::string name_; std::vector> deduced_parameters_; + std::optional> me_pattern_; Nonnull param_pattern_; ReturnTerm return_term_; std::optional> body_; @@ -268,7 +199,7 @@ class ClassDeclaration : public Declaration { using ImplementsCarbonNamedEntity = void; ClassDeclaration(SourceLocation source_loc, std::string name, - std::vector> members) + std::vector> members) : Declaration(AstNodeKind::ClassDeclaration, source_loc), name_(std::move(name)), members_(std::move(members)) {} @@ -278,7 +209,9 @@ class ClassDeclaration : public Declaration { } auto name() const -> const std::string& { return name_; } - auto members() const -> llvm::ArrayRef> { return members_; } + auto members() const -> llvm::ArrayRef> { + return members_; + } auto value_category() const -> ValueCategory { return ValueCategory::Let; } auto constant_value() const -> std::optional> { @@ -294,7 +227,7 @@ class ClassDeclaration : public Declaration { private: std::string name_; - std::vector> members_; + std::vector> members_; std::optional> constant_value_; }; @@ -367,7 +300,7 @@ class VariableDeclaration : public Declaration { public: VariableDeclaration(SourceLocation source_loc, Nonnull binding, - Nonnull initializer) + std::optional> initializer) : Declaration(AstNodeKind::VariableDeclaration, source_loc), binding_(binding), initializer_(initializer) {} @@ -378,15 +311,17 @@ class VariableDeclaration : public Declaration { auto binding() const -> const BindingPattern& { return *binding_; } auto binding() -> BindingPattern& { return *binding_; } - auto initializer() const -> const Expression& { return *initializer_; } - auto initializer() -> Expression& { return *initializer_; } + auto initializer() const -> const Expression& { return **initializer_; } + auto initializer() -> Expression& { return **initializer_; } + + bool has_initializer() const { return initializer_.has_value(); } private: // 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. Nonnull binding_; - Nonnull initializer_; + std::optional> initializer_; }; } // namespace Carbon diff --git a/executable_semantics/ast/member.cpp b/executable_semantics/ast/member.cpp deleted file mode 100644 index d9fd75f92363..000000000000 --- a/executable_semantics/ast/member.cpp +++ /dev/null @@ -1,25 +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/ast/member.h" - -#include "executable_semantics/common/arena.h" -#include "llvm/Support/Casting.h" - -namespace Carbon { - -using llvm::cast; - -Member::~Member() = default; - -void Member::Print(llvm::raw_ostream& out) const { - switch (kind()) { - case MemberKind::FieldMember: - const auto& field = cast(*this); - out << "var " << field.binding() << ";\n"; - break; - } -} - -} // namespace Carbon diff --git a/executable_semantics/ast/member.h b/executable_semantics/ast/member.h deleted file mode 100644 index 5aa926b89f65..000000000000 --- a/executable_semantics/ast/member.h +++ /dev/null @@ -1,70 +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 - -#ifndef EXECUTABLE_SEMANTICS_AST_MEMBER_H_ -#define EXECUTABLE_SEMANTICS_AST_MEMBER_H_ - -#include - -#include "common/ostream.h" -#include "executable_semantics/ast/expression.h" -#include "executable_semantics/ast/pattern.h" -#include "executable_semantics/ast/source_location.h" -#include "llvm/Support/Compiler.h" - -namespace Carbon { - -// Abstract base class of all AST nodes representing patterns. -// -// Member and its derived classes support LLVM-style RTTI, including -// llvm::isa, llvm::cast, and llvm::dyn_cast. To support this, every -// class derived from Member 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 Member : public AstNode { - public: - ~Member() override = 0; - - Member(const Member&) = delete; - auto operator=(const Member&) -> Member& = delete; - - void Print(llvm::raw_ostream& out) const override; - - static auto classof(const AstNode* node) -> bool { - return InheritsFromMember(node->kind()); - } - - // Returns the enumerator corresponding to the most-derived type of this - // object. - auto kind() const -> MemberKind { - return static_cast(root_kind()); - } - - protected: - Member(AstNodeKind kind, SourceLocation source_loc) - : AstNode(kind, source_loc) {} -}; - -class FieldMember : public Member { - public: - FieldMember(SourceLocation source_loc, Nonnull binding) - : Member(AstNodeKind::FieldMember, source_loc), binding_(binding) { - CHECK(binding->name() != AnonymousName); - } - - static auto classof(const AstNode* node) -> bool { - return InheritsFromFieldMember(node->kind()); - } - - auto binding() const -> const BindingPattern& { return *binding_; } - auto binding() -> BindingPattern& { return *binding_; } - - private: - Nonnull binding_; -}; - -} // namespace Carbon - -#endif // EXECUTABLE_SEMANTICS_AST_MEMBER_H_ diff --git a/executable_semantics/ast/pattern.h b/executable_semantics/ast/pattern.h index acb0f2ca80b9..21d034961549 100644 --- a/executable_semantics/ast/pattern.h +++ b/executable_semantics/ast/pattern.h @@ -15,6 +15,7 @@ #include "executable_semantics/ast/expression.h" #include "executable_semantics/ast/source_location.h" #include "executable_semantics/ast/static_scope.h" +#include "executable_semantics/ast/value_category.h" #include "llvm/ADT/ArrayRef.h" namespace Carbon { diff --git a/executable_semantics/ast/return_term.h b/executable_semantics/ast/return_term.h new file mode 100644 index 000000000000..bc48eaf9b5cd --- /dev/null +++ b/executable_semantics/ast/return_term.h @@ -0,0 +1,102 @@ +// 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_RETURN_TERM_H_ +#define EXECUTABLE_SEMANTICS_AST_RETURN_TERM_H_ + +#include +#include + +#include "common/check.h" +#include "common/ostream.h" +#include "executable_semantics/ast/source_location.h" +#include "executable_semantics/common/nonnull.h" + +namespace Carbon { + +class Value; +class Expression; + +// The syntactic representation of a function declaration's return type. +// This syntax can take one of three forms: +// - An _explicit_ term consists of `->` followed by a type expression. +// - An _auto_ term consists of `-> auto`. +// - An _omitted_ term consists of no tokens at all. +// Each of these forms has a corresponding factory function. +class ReturnTerm { + public: + ReturnTerm(const ReturnTerm&) = default; + auto operator=(const ReturnTerm&) -> ReturnTerm& = default; + + // Represents an omitted return term at `source_loc`. + static auto Omitted(SourceLocation source_loc) -> ReturnTerm { + return ReturnTerm(ReturnKind::Omitted, source_loc); + } + + // Represents an auto return term at `source_loc`. + static auto Auto(SourceLocation source_loc) -> ReturnTerm { + return ReturnTerm(ReturnKind::Auto, source_loc); + } + + // Represents an explicit return term with the given type expression. + static auto Explicit(Nonnull type_expression) -> ReturnTerm { + return ReturnTerm(type_expression); + } + + // Returns true if this represents an omitted return term. + auto is_omitted() const -> bool { return kind_ == ReturnKind::Omitted; } + + // Returns true if this represents an auto return term. + auto is_auto() const -> bool { return kind_ == ReturnKind::Auto; } + + // If this represents an explicit return term, returns the type expression. + // Otherwise, returns nullopt. + auto type_expression() const -> std::optional> { + return type_expression_; + } + auto type_expression() -> std::optional> { + return type_expression_; + } + + // The static return type this term resolves to. Cannot be called before + // typechecking. + auto static_type() const -> const Value& { return **static_type_; } + + // Sets the value of static_type(). Can only be called once, during + // typechecking. + void set_static_type(Nonnull type) { static_type_ = type; } + + // Returns whether static_type() has been set. Should only be called + // during typechecking: before typechecking it's guaranteed to be false, + // and after typechecking it's guaranteed to be true. + auto has_static_type() const -> bool { return static_type_.has_value(); } + + auto source_loc() const -> SourceLocation { return source_loc_; } + + void Print(llvm::raw_ostream& out) const; + LLVM_DUMP_METHOD void Dump() const { Print(llvm::errs()); } + + private: + enum class ReturnKind { Omitted, Auto, Expression }; + + explicit ReturnTerm(ReturnKind kind, SourceLocation source_loc) + : kind_(kind), source_loc_(source_loc) { + CHECK(kind != ReturnKind::Expression); + } + + explicit ReturnTerm(Nonnull type_expression) + : kind_(ReturnKind::Expression), + type_expression_(type_expression), + source_loc_(type_expression->source_loc()) {} + + ReturnKind kind_; + std::optional> type_expression_; + std::optional> static_type_; + + SourceLocation source_loc_; +}; + +} // namespace Carbon + +#endif // EXECUTABLE_SEMANTICS_AST_RETURN_TERM_H_ diff --git a/executable_semantics/ast/statement.h b/executable_semantics/ast/statement.h index fbf5e6fd30ef..508ed1113245 100644 --- a/executable_semantics/ast/statement.h +++ b/executable_semantics/ast/statement.h @@ -9,8 +9,10 @@ #include #include "common/ostream.h" +#include "executable_semantics/ast/ast_node.h" #include "executable_semantics/ast/expression.h" #include "executable_semantics/ast/pattern.h" +#include "executable_semantics/ast/return_term.h" #include "executable_semantics/ast/source_location.h" #include "executable_semantics/ast/static_scope.h" #include "executable_semantics/ast/value_category.h" diff --git a/executable_semantics/interpreter/BUILD b/executable_semantics/interpreter/BUILD index 316ad38e3a89..393ac25cba9c 100644 --- a/executable_semantics/interpreter/BUILD +++ b/executable_semantics/interpreter/BUILD @@ -149,6 +149,7 @@ cc_library( "//common:check", "//executable_semantics/ast", "//executable_semantics/ast:declaration", + "//executable_semantics/ast:return_term", "//executable_semantics/ast:statement", "//executable_semantics/common:error", "//executable_semantics/common:nonnull", @@ -165,8 +166,8 @@ cc_library( "//executable_semantics/ast", "//executable_semantics/ast:declaration", "//executable_semantics/ast:expression", - "//executable_semantics/ast:member", "//executable_semantics/ast:pattern", + "//executable_semantics/ast:return_term", "//executable_semantics/ast:statement", "//executable_semantics/ast:static_scope", "//executable_semantics/common:arena", diff --git a/executable_semantics/interpreter/exec_program.cpp b/executable_semantics/interpreter/exec_program.cpp index 4a8962eafdcb..987da6a01f74 100644 --- a/executable_semantics/interpreter/exec_program.cpp +++ b/executable_semantics/interpreter/exec_program.cpp @@ -22,7 +22,6 @@ void ExecProgram(Nonnull arena, AST ast, bool trace) { for (const auto decl : ast.declarations) { llvm::outs() << *decl; } - llvm::outs() << "********** type checking **********\n"; } SourceLocation source_loc("", 0); ast.main_call = arena->New( @@ -30,8 +29,17 @@ void ExecProgram(Nonnull arena, AST ast, bool trace) { arena->New(source_loc)); // Although name resolution is currently done once, generic programming // (particularly templates) may require more passes. + if (trace) { + llvm::outs() << "********** resolving names **********\n"; + } ResolveNames(ast); + if (trace) { + llvm::outs() << "********** resolving control flow **********\n"; + } ResolveControlFlow(ast); + if (trace) { + llvm::outs() << "********** type checking **********\n"; + } TypeChecker(arena, trace).TypeCheck(ast); if (trace) { llvm::outs() << "\n"; diff --git a/executable_semantics/interpreter/interpreter.cpp b/executable_semantics/interpreter/interpreter.cpp index 7c92cbcdf7a1..45b246813a44 100644 --- a/executable_semantics/interpreter/interpreter.cpp +++ b/executable_semantics/interpreter/interpreter.cpp @@ -316,9 +316,11 @@ void Interpreter::StepLvalue() { } } case ExpressionKind::PrimitiveOperatorExpression: { - const PrimitiveOperatorExpression& op = cast(exp); + const PrimitiveOperatorExpression& op = + cast(exp); if (op.op() != Operator::Deref) { - FATAL() << "Can't treat primitive operator expression as lvalue: " << exp; + FATAL() << "Can't treat primitive operator expression as lvalue: " + << exp; } if (act.pos() == 0) { return todo_.Spawn( @@ -355,6 +357,7 @@ auto Interpreter::Convert(Nonnull value, switch (value->kind()) { case Value::Kind::IntValue: case Value::Kind::FunctionValue: + case Value::Kind::BoundMethodValue: case Value::Kind::PointerValue: case Value::Kind::LValue: case Value::Kind::BoolValue: @@ -573,6 +576,23 @@ void Interpreter::StepExp() { std::make_unique(*function.body()), std::move(function_scope)); } + case Value::Kind::BoundMethodValue: { + const BoundMethodValue& m = + cast(*act.results()[0]); + const FunctionDeclaration& method = m.declaration(); + Nonnull converted_args = Convert( + act.results()[1], &method.param_pattern().static_type()); + RuntimeScope method_scope(&heap_); + CHECK(PatternMatch(&method.me_pattern().value(), m.receiver(), + exp.source_loc(), &method_scope)); + CHECK(PatternMatch(&method.param_pattern().value(), converted_args, + exp.source_loc(), &method_scope)); + CHECK(method.body().has_value()) + << "Calling a method that's missing a body"; + return todo_.Spawn( + std::make_unique(*method.body()), + std::move(method_scope)); + } default: FATAL_RUNTIME_ERROR(exp.source_loc()) << "in call, expected a function, not " << *act.results()[0]; @@ -928,11 +948,15 @@ void Interpreter::StepDeclaration() { switch (decl.kind()) { case DeclarationKind::VariableDeclaration: { const auto& var_decl = cast(decl); - if (act.pos() == 0) { - return todo_.Spawn( - std::make_unique(&var_decl.initializer())); + if (var_decl.has_initializer()) { + if (act.pos() == 0) { + return todo_.Spawn( + std::make_unique(&var_decl.initializer())); + } else { + todo_.Initialize(&var_decl.binding(), act.results()[0]); + return todo_.FinishAction(); + } } else { - todo_.Initialize(&var_decl.binding(), act.results()[0]); return todo_.FinishAction(); } } diff --git a/executable_semantics/interpreter/resolve_control_flow.cpp b/executable_semantics/interpreter/resolve_control_flow.cpp index 4939094929ea..1288170f3a08 100644 --- a/executable_semantics/interpreter/resolve_control_flow.cpp +++ b/executable_semantics/interpreter/resolve_control_flow.cpp @@ -5,6 +5,7 @@ #include "executable_semantics/interpreter/resolve_control_flow.h" #include "executable_semantics/ast/declaration.h" +#include "executable_semantics/ast/return_term.h" #include "executable_semantics/ast/statement.h" #include "executable_semantics/common/error.h" #include "llvm/Support/Casting.h" @@ -110,16 +111,32 @@ static void ResolveControlFlow(Nonnull statement, } } +void ResolveControlFlow(Nonnull declaration) { + switch (declaration->kind()) { + case DeclarationKind::FunctionDeclaration: { + auto& function = cast(*declaration); + if (function.body().has_value()) { + FunctionData data = {.declaration = &function}; + ResolveControlFlow(*function.body(), std::nullopt, &data); + } + break; + } + case DeclarationKind::ClassDeclaration: { + auto& class_decl = cast(*declaration); + for (Nonnull member : class_decl.members()) { + ResolveControlFlow(member); + } + break; + } + default: + // do nothing + break; + } +} + void ResolveControlFlow(AST& ast) { for (auto declaration : ast.declarations) { - if (declaration->kind() != DeclarationKind::FunctionDeclaration) { - continue; - } - auto& function = cast(*declaration); - if (function.body().has_value()) { - FunctionData data = {.declaration = &function}; - ResolveControlFlow(*function.body(), std::nullopt, &data); - } + ResolveControlFlow(declaration); } } diff --git a/executable_semantics/interpreter/resolve_names.cpp b/executable_semantics/interpreter/resolve_names.cpp index 0068fa069233..3afb23505506 100644 --- a/executable_semantics/interpreter/resolve_names.cpp +++ b/executable_semantics/interpreter/resolve_names.cpp @@ -8,7 +8,6 @@ #include "executable_semantics/ast/declaration.h" #include "executable_semantics/ast/expression.h" -#include "executable_semantics/ast/member.h" #include "executable_semantics/ast/pattern.h" #include "executable_semantics/ast/statement.h" #include "executable_semantics/ast/static_scope.h" @@ -21,20 +20,6 @@ namespace Carbon { // Adds the names exposed by the given AST node to enclosing_scope. static void AddExposedNames(const Declaration& declaration, StaticScope& enclosing_scope); -static void AddExposedNames(const Member& member, StaticScope& enclosing_scope); - -static void AddExposedNames(const Member& member, - StaticScope& enclosing_scope) { - switch (member.kind()) { - case MemberKind::FieldMember: { - const auto& field = cast(member); - if (field.binding().name() != AnonymousName) { - enclosing_scope.Add(field.binding().name(), &field.binding()); - } - break; - } - } -} static void AddExposedNames(const Declaration& declaration, StaticScope& enclosing_scope) { @@ -77,7 +62,6 @@ static void ResolveNames(Expression& expression, const StaticScope& enclosing_scope); static void ResolveNames(Pattern& pattern, StaticScope& enclosing_scope); static void ResolveNames(Statement& statement, StaticScope& enclosing_scope); -static void ResolveNames(Member& member, StaticScope& enclosing_scope); static void ResolveNames(Declaration& declaration, StaticScope& enclosing_scope); @@ -257,13 +241,6 @@ static void ResolveNames(Statement& statement, StaticScope& enclosing_scope) { } } -static void ResolveNames(Member& member, StaticScope& enclosing_scope) { - switch (member.kind()) { - case MemberKind::FieldMember: - ResolveNames(cast(member).binding(), enclosing_scope); - } -} - static void ResolveNames(Declaration& declaration, StaticScope& enclosing_scope) { switch (declaration.kind()) { @@ -275,6 +252,9 @@ static void ResolveNames(Declaration& declaration, function_scope.Add(binding->name(), binding); ResolveNames(binding->type(), function_scope); } + if (function.is_method()) { + ResolveNames(function.me_pattern(), function_scope); + } ResolveNames(function.param_pattern(), function_scope); if (function.return_term().type_expression().has_value()) { ResolveNames(**function.return_term().type_expression(), @@ -289,10 +269,11 @@ static void ResolveNames(Declaration& declaration, auto& class_decl = cast(declaration); StaticScope class_scope; class_scope.AddParent(&enclosing_scope); - for (Nonnull member : class_decl.members()) { + class_scope.Add(class_decl.name(), &class_decl); + for (Nonnull member : class_decl.members()) { AddExposedNames(*member, class_scope); } - for (Nonnull member : class_decl.members()) { + for (Nonnull member : class_decl.members()) { ResolveNames(*member, class_scope); } break; @@ -316,7 +297,9 @@ static void ResolveNames(Declaration& declaration, case DeclarationKind::VariableDeclaration: { auto& var = cast(declaration); ResolveNames(var.binding(), enclosing_scope); - ResolveNames(var.initializer(), enclosing_scope); + if (var.has_initializer()) { + ResolveNames(var.initializer(), enclosing_scope); + } break; } } diff --git a/executable_semantics/interpreter/type_checker.cpp b/executable_semantics/interpreter/type_checker.cpp index a0ce25b538a4..7ad0ed7f09f8 100644 --- a/executable_semantics/interpreter/type_checker.cpp +++ b/executable_semantics/interpreter/type_checker.cpp @@ -74,6 +74,7 @@ static auto IsConcreteType(Nonnull value) -> bool { switch (value->kind()) { case Value::Kind::IntValue: case Value::Kind::FunctionValue: + case Value::Kind::BoundMethodValue: case Value::Kind::PointerValue: case Value::Kind::LValue: case Value::Kind::BoolValue: @@ -165,7 +166,7 @@ static auto IsImplicitlyConvertible(Nonnull source, case Value::Kind::NominalClassType: return FieldTypesImplicitlyConvertible( cast(*source).fields(), - cast(*destination).fields()); + FieldTypes(cast(*destination))); default: return false; } @@ -314,6 +315,7 @@ void TypeChecker::ArgumentDeduction( case Value::Kind::IntValue: case Value::Kind::BoolValue: case Value::Kind::FunctionValue: + case Value::Kind::BoundMethodValue: case Value::Kind::PointerValue: case Value::Kind::LValue: case Value::Kind::StructValue: @@ -380,6 +382,7 @@ auto TypeChecker::Substitute( case Value::Kind::IntValue: case Value::Kind::BoolValue: case Value::Kind::FunctionValue: + case Value::Kind::BoundMethodValue: case Value::Kind::PointerValue: case Value::Kind::LValue: case Value::Kind::StructValue: @@ -480,25 +483,28 @@ void TypeChecker::TypeCheckExp(Nonnull e) { } case Value::Kind::NominalClassType: { const auto& t_class = cast(aggregate_type); - // Search for a field - for (auto& field : t_class.fields()) { - if (access.field() == field.name) { - SetStaticType(&access, field.value); - access.set_value_category(access.aggregate().value_category()); - return; + if (std::optional> member = + t_class.FindMember(access.field()); + member.has_value()) { + SetStaticType(&access, &(*member)->static_type()); + switch ((*member)->kind()) { + case DeclarationKind::VariableDeclaration: + access.set_value_category(access.aggregate().value_category()); + break; + case DeclarationKind::FunctionDeclaration: + access.set_value_category(ValueCategory::Let); + break; + default: + FATAL() << "member " << access.field() + << " is not a field or method"; + break; } + return; + } else { + FATAL_COMPILATION_ERROR(e->source_loc()) + << "class " << t_class.declaration().name() + << " does not have a field named " << access.field(); } - // Search for a method - for (auto& method : t_class.methods()) { - if (access.field() == method.name) { - SetStaticType(&access, method.value); - access.set_value_category(ValueCategory::Let); - return; - } - } - FATAL_COMPILATION_ERROR(e->source_loc()) - << "class " << t_class.name() << " does not have a field named " - << access.field(); } case Value::Kind::TypeOfChoiceType: { const ChoiceType& choice = @@ -517,10 +523,36 @@ void TypeChecker::TypeCheckExp(Nonnull e) { access.set_value_category(ValueCategory::Let); return; } + case Value::Kind::TypeOfClassType: { + const NominalClassType& class_type = + cast(aggregate_type).class_type(); + if (std::optional> member = + class_type.FindMember(access.field()); + member.has_value()) { + switch ((*member)->kind()) { + case DeclarationKind::FunctionDeclaration: { + const auto& func = cast(*member); + if (func->is_method()) { + break; + } + SetStaticType(&access, &(*member)->static_type()); + access.set_value_category(ValueCategory::Let); + return; + } + default: + break; + } + FATAL_COMPILATION_ERROR(access.source_loc()) + << access.field() << " is not a class function"; + } else { + FATAL_COMPILATION_ERROR(access.source_loc()) + << class_type << " does not have a class function named " + << access.field(); + } + } default: FATAL_COMPILATION_ERROR(e->source_loc()) - << "field access, expected a struct\n" - << *e; + << "field access, unexpected " << aggregate_type << " in " << *e; } } case ExpressionKind::IdentifierExpression: { @@ -623,8 +655,9 @@ void TypeChecker::TypeCheckExp(Nonnull e) { return; case Operator::AddressOf: if (op.arguments()[0]->value_category() != ValueCategory::Var) { - FATAL_COMPILATION_ERROR(op.arguments()[0]->source_loc()) << - "Argument to " << ToString(op.op()) << " should be an lvalue."; + FATAL_COMPILATION_ERROR(op.arguments()[0]->source_loc()) + << "Argument to " << ToString(op.op()) + << " should be an lvalue."; } SetStaticType(&op, arena_->New(ts[0])); op.set_value_category(ValueCategory::Let); @@ -1001,6 +1034,11 @@ void TypeChecker::TypeCheckFunctionDeclaration(Nonnull f, SetStaticType(deduced, arena_->New(deduced)); SetConstantValue(deduced, &deduced->static_type()); } + if (f->is_method()) { + // Type check the receiver patter + TypeCheckPattern(&f->me_pattern(), std::nullopt); + } + // Type check the parameter pattern TypeCheckPattern(&f->param_pattern(), std::nullopt); @@ -1044,32 +1082,32 @@ void TypeChecker::TypeCheckFunctionDeclaration(Nonnull f, arena_->New(), &f->return_term().static_type()); // TODO: Check that main doesn't have any parameters. } + SetConstantValue(f, arena_->New(f)); return; } void TypeChecker::TypeCheckClassDeclaration( Nonnull class_decl) { - std::vector fields; - std::vector methods; - for (Nonnull m : class_decl->members()) { - switch (m->kind()) { - case MemberKind::FieldMember: { - BindingPattern& binding = cast(*m).binding(); - if (binding.name() == AnonymousName) { - FATAL_COMPILATION_ERROR(binding.source_loc()) - << "Struct members must have names"; - } - TypeCheckPattern(&binding, std::nullopt); - fields.push_back( - {.name = binding.name(), .value = &binding.static_type()}); - break; - } - } + // The declarations of the members may refer to the class, so we + // must set the constant value of the class and its static type + // before we start processing the members. + Nonnull class_type = + arena_->New(class_decl); + SetConstantValue(class_decl, class_type); + SetStaticType(class_decl, arena_->New(class_type)); + + // First pass: process the field, class function, and method + // declarations but not the bodies of class functions or method + // declarations. + for (Nonnull m : class_decl->members()) { + DeclareDeclaration(m); + } + + // Second pass: type check the bodies of the class functions and + // methods. + for (Nonnull m : class_decl->members()) { + TypeCheckDeclaration(m); } - SetStaticType( - class_decl, - arena_->New(arena_->New( - class_decl->name(), std::move(fields), std::move(methods)))); } void TypeChecker::TypeCheckChoiceDeclaration( @@ -1081,12 +1119,13 @@ void TypeChecker::TypeCheckChoiceDeclaration( alternatives.push_back({.name = alternative->name(), .value = signature}); } auto ct = arena_->New(choice->name(), std::move(alternatives)); + SetConstantValue(choice, ct); SetStaticType(choice, arena_->New(ct)); } void TypeChecker::TypeCheck(AST& ast) { for (Nonnull declaration : ast.declarations) { - TopLevel(declaration); + DeclareDeclaration(declaration); } for (Nonnull decl : ast.declarations) { TypeCheckDeclaration(decl); @@ -1111,7 +1150,9 @@ void TypeChecker::TypeCheckDeclaration(Nonnull d) { // Signals a type error if the initializing expression does not have // the declared type of the variable, otherwise returns this // declaration with annotated types. - TypeCheckExp(&var.initializer()); + if (var.has_initializer()) { + TypeCheckExp(&var.initializer()); + } const auto* binding_type = dyn_cast(&var.binding().type()); if (binding_type == nullptr) { @@ -1122,35 +1163,32 @@ void TypeChecker::TypeCheckDeclaration(Nonnull d) { Nonnull declared_type = InterpExp(&binding_type->expression(), arena_, trace_); SetStaticType(&var, declared_type); - ExpectType(var.source_loc(), "initializer of variable", declared_type, - &var.initializer().static_type()); + if (var.has_initializer()) { + ExpectType(var.source_loc(), "initializer of variable", declared_type, + &var.initializer().static_type()); + } return; } } } -void TypeChecker::TopLevel(Nonnull d) { +void TypeChecker::DeclareDeclaration(Nonnull d) { switch (d->kind()) { case DeclarationKind::FunctionDeclaration: { auto& func_def = cast(*d); TypeCheckFunctionDeclaration(&func_def, /*check_body=*/false); - SetConstantValue(&func_def, arena_->New(&func_def)); break; } case DeclarationKind::ClassDeclaration: { auto& class_decl = cast(*d); TypeCheckClassDeclaration(&class_decl); - const auto& type = cast(class_decl.static_type()); - SetConstantValue(&class_decl, &type.class_type()); break; } case DeclarationKind::ChoiceDeclaration: { auto& choice = cast(*d); TypeCheckChoiceDeclaration(&choice); - const auto& type = cast(choice.static_type()); - SetConstantValue(&choice, &type.choice_type()); break; } diff --git a/executable_semantics/interpreter/type_checker.h b/executable_semantics/interpreter/type_checker.h index 522fbc68f0a0..afe2dcb1b7f7 100644 --- a/executable_semantics/interpreter/type_checker.h +++ b/executable_semantics/interpreter/type_checker.h @@ -71,7 +71,9 @@ class TypeChecker { // Equivalent to TypeCheckExp, but operates on the AST rooted at choice_decl. void TypeCheckChoiceDeclaration(Nonnull choice); - void TopLevel(Nonnull d); + // Establish the type of the declaration without deeply checking + // the declaration, such as checking the body of a function. + void DeclareDeclaration(Nonnull d); // Verifies that opt_stmt holds a statement, and it is structurally impossible // for control flow to leave that statement except via a `return`. diff --git a/executable_semantics/interpreter/value.cpp b/executable_semantics/interpreter/value.cpp index 765e5b791456..d58446874702 100644 --- a/executable_semantics/interpreter/value.cpp +++ b/executable_semantics/interpreter/value.cpp @@ -40,10 +40,27 @@ static auto GetMember(Nonnull arena, Nonnull v, return *field; } case Value::Kind::NominalClassValue: { + const NominalClassValue& object = cast(*v); + // Look for a field std::optional> field = - cast(cast(*v).inits()).FindField(f); + cast(object.inits()).FindField(f); if (field == std::nullopt) { - FATAL_RUNTIME_ERROR(source_loc) << "member " << f << " not in " << *v; + // Look for a method in the object's class + const NominalClassType& class_type = + cast(object.type()); + std::optional> func = + class_type.FindFunction(f); + if (func == std::nullopt) { + FATAL_RUNTIME_ERROR(source_loc) << "member " << f << " not in " << *v + << " or its class " << class_type; + } else if ((*func)->declaration().is_method()) { + // Found a method. Turn it into a bound method. + const FunctionValue& m = cast(**func); + return arena->New(&m.declaration(), &object); + } else { + // Found a class function + return *func; + } } return *field; } @@ -55,6 +72,16 @@ static auto GetMember(Nonnull arena, Nonnull v, } return arena->New(f, choice.name()); } + case Value::Kind::NominalClassType: { + const NominalClassType& class_type = cast(*v); + std::optional> fun = + class_type.FindFunction(f); + if (fun == std::nullopt) { + FATAL_RUNTIME_ERROR(source_loc) + << "class function " << f << " not in " << *v; + } + return *fun; + } default: FATAL() << "field access not allowed for value " << *v; } @@ -158,7 +185,7 @@ void Value::Print(llvm::raw_ostream& out) const { } case Value::Kind::NominalClassValue: { const auto& s = cast(*this); - out << cast(s.type()).name() << s.inits(); + out << cast(s.type()).declaration().name() << s.inits(); break; } case Value::Kind::TupleValue: { @@ -179,6 +206,10 @@ void Value::Print(llvm::raw_ostream& out) const { case Value::Kind::FunctionValue: out << "fun<" << cast(*this).declaration().name() << ">"; break; + case Value::Kind::BoundMethodValue: + out << "bound_method<" + << cast(*this).declaration().name() << ">"; + break; case Value::Kind::PointerValue: out << "ptr<" << cast(*this).address() << ">"; break; @@ -230,9 +261,11 @@ void Value::Print(llvm::raw_ostream& out) const { out << "}"; break; } - case Value::Kind::NominalClassType: - out << "class " << cast(*this).name(); + case Value::Kind::NominalClassType: { + const NominalClassType& class_type = cast(*this); + out << "class " << class_type.declaration().name(); break; + } case Value::Kind::ChoiceType: out << "choice " << cast(*this).name(); break; @@ -252,7 +285,8 @@ void Value::Print(llvm::raw_ostream& out) const { out << "\""; break; case Value::Kind::TypeOfClassType: - out << "typeof(" << cast(*this).class_type().name() + out << "typeof(" + << cast(*this).class_type().declaration().name() << ")"; break; case Value::Kind::TypeOfChoiceType: @@ -328,8 +362,8 @@ auto TypeEqual(Nonnull t1, Nonnull t2) -> bool { return true; } case Value::Kind::NominalClassType: - return cast(*t1).name() == - cast(*t2).name(); + return cast(*t1).declaration().name() == + cast(*t2).declaration().name(); case Value::Kind::ChoiceType: return cast(*t1).name() == cast(*t2).name(); case Value::Kind::TupleValue: { @@ -387,6 +421,15 @@ auto ValueEqual(Nonnull v1, Nonnull v2) -> bool { return body1.has_value() == body2.has_value() && (!body1.has_value() || *body1 == *body2); } + case Value::Kind::BoundMethodValue: { + const BoundMethodValue& m1 = cast(*v1); + const BoundMethodValue& m2 = cast(*v2); + std::optional> body1 = m1.declaration().body(); + std::optional> body2 = m2.declaration().body(); + return ValueEqual(m1.receiver(), m2.receiver()) && + body1.has_value() == body2.has_value() && + (!body1.has_value() || *body1 == *body2); + } case Value::Kind::TupleValue: { const std::vector>& elements1 = cast(*v1).elements(); @@ -455,4 +498,64 @@ auto ChoiceType::FindAlternative(std::string_view name) const return std::nullopt; } +auto NominalClassType::FindFunction(const std::string& name) const + -> std::optional> { + for (const auto& member : declaration().members()) { + switch (member->kind()) { + case DeclarationKind::FunctionDeclaration: { + const auto& fun = cast(*member); + if (fun.name() == name) { + return &cast(**fun.constant_value()); + } + break; + } + default: + break; + } + } + return std::nullopt; +} + +auto FieldTypes(const NominalClassType& class_type) -> std::vector { + std::vector field_types; + for (Nonnull m : class_type.declaration().members()) { + switch (m->kind()) { + case DeclarationKind::VariableDeclaration: { + const auto& var = cast(*m); + field_types.push_back({.name = var.binding().name(), + .value = &var.binding().static_type()}); + break; + } + default: + break; + } + } + return field_types; +} + +auto NominalClassType::FindMember(const std::string& name) const + -> std::optional> { + for (const auto& member : declaration().members()) { + switch (member->kind()) { + case DeclarationKind::FunctionDeclaration: { + const auto& fun = cast(*member); + if (fun.name() == name) { + return &fun; + } + break; + } + case DeclarationKind::VariableDeclaration: { + const auto& var = cast(*member); + if (var.binding().name() == name) { + return &var; + } + break; + } + default: + break; + } + } + return std::nullopt; +} + } // namespace Carbon diff --git a/executable_semantics/interpreter/value.h b/executable_semantics/interpreter/value.h index d550e7ca11ff..ba88cac99610 100644 --- a/executable_semantics/interpreter/value.h +++ b/executable_semantics/interpreter/value.h @@ -36,6 +36,7 @@ class Value { enum class Kind { IntValue, FunctionValue, + BoundMethodValue, PointerValue, LValue, BoolValue, @@ -135,6 +136,30 @@ class FunctionValue : public Value { Nonnull declaration_; }; +// A bound method value. It includes the receiver object. +class BoundMethodValue : public Value { + public: + explicit BoundMethodValue(Nonnull declaration, + Nonnull receiver) + : Value(Kind::BoundMethodValue), + declaration_(declaration), + receiver_(receiver) {} + + static auto classof(const Value* value) -> bool { + return value->kind() == Kind::BoundMethodValue; + } + + auto declaration() const -> const FunctionDeclaration& { + return *declaration_; + } + + auto receiver() const -> Nonnull { return receiver_; } + + private: + Nonnull declaration_; + Nonnull receiver_; +}; + // The value of a location in memory. class LValue : public Value { public: @@ -212,7 +237,7 @@ class StructValue : public Value { std::vector elements_; }; -// A value of a nominal class type. +// A value of a nominal class type, i.e., an object. class NominalClassValue : public Value { public: NominalClassValue(Nonnull type, Nonnull inits) @@ -227,7 +252,7 @@ class NominalClassValue : public Value { private: Nonnull type_; - Nonnull inits_; + Nonnull inits_; // The initializing StructValue. }; // An alternative constructor value. @@ -429,27 +454,30 @@ class StructType : public Value { // A class type. class NominalClassType : public Value { public: - NominalClassType(std::string name, std::vector fields, - std::vector methods) - : Value(Kind::NominalClassType), - name_(std::move(name)), - fields_(std::move(fields)), - methods_(std::move(methods)) {} + NominalClassType(Nonnull declaration) + : Value(Kind::NominalClassType), declaration_(declaration) {} static auto classof(const Value* value) -> bool { return value->kind() == Kind::NominalClassType; } - auto name() const -> const std::string& { return name_; } - auto fields() const -> llvm::ArrayRef { return fields_; } - auto methods() const -> llvm::ArrayRef { return methods_; } + auto declaration() const -> const ClassDeclaration& { return *declaration_; } + + // Return the declaration of the member with the given name. + auto FindMember(const std::string& name) const + -> std::optional>; + + // Returns the value of the function named `name` in this class, or + // nullopt if there is no such function. + auto FindFunction(const std::string& name) const + -> std::optional>; private: - std::string name_; - std::vector fields_; - std::vector methods_; + Nonnull declaration_; }; +auto FieldTypes(const NominalClassType&) -> std::vector; + // A choice type. class ChoiceType : public Value { public: diff --git a/executable_semantics/syntax/parser.ypp b/executable_semantics/syntax/parser.ypp index 6f283531b849..9485f24653ff 100644 --- a/executable_semantics/syntax/parser.ypp +++ b/executable_semantics/syntax/parser.ypp @@ -110,8 +110,8 @@ %type >> statement_list %type > expression %type > generic_binding -%type >> deduced_params -%type >> deduced_param_list +%type >> deduced_params +%type >> deduced_param_list %type > pattern %type > non_expression_pattern %type > return_term @@ -122,9 +122,8 @@ %type > struct_type_literal_contents %type > tuple %type binding_lhs +%type >> receiver %type > variable_declaration -%type > member -%type >> member_list %type > paren_expression_base %type > paren_expression_contents %type > paren_pattern @@ -653,10 +652,10 @@ generic_binding: ; deduced_param_list: // Empty - { $$ = std::vector>(); } + { $$ = std::vector>(); } | generic_binding { - $$ = std::vector>(); + $$ = std::vector>(); $$.push_back($1); } | generic_binding COMMA deduced_param_list @@ -664,40 +663,44 @@ deduced_param_list: $$ = $3; $$.push_back($1); } +| variable_declaration + { + $$ = std::vector>(); + $$.push_back($1); + } +| variable_declaration COMMA deduced_param_list + { + $$ = $3; + $$.push_back($1); + } ; deduced_params: // Empty - { $$ = std::vector>(); } + { $$ = std::vector>(); } | LEFT_SQUARE_BRACKET deduced_param_list RIGHT_SQUARE_BRACKET { $$ = $2; } ; +receiver: + // Empty + { $$ = std::nullopt; } +| LEFT_CURLY_BRACE variable_declaration RIGHT_CURLY_BRACE + { $$ = $2; } +; function_declaration: - FN identifier deduced_params maybe_empty_tuple_pattern return_term block + FN identifier deduced_params receiver maybe_empty_tuple_pattern return_term block { $$ = arena->New(context.source_loc(), $2, $3, $4, $5, - $6); + $6, $7); } -| FN identifier deduced_params maybe_empty_tuple_pattern return_term SEMICOLON +| FN identifier deduced_params receiver maybe_empty_tuple_pattern return_term SEMICOLON { $$ = arena->New(context.source_loc(), $2, $3, $4, $5, - std::nullopt); + $6, std::nullopt); } ; variable_declaration: identifier COLON pattern { $$ = arena->New(context.source_loc(), $1, $3); } ; -member: VAR variable_declaration SEMICOLON - { $$ = arena->New(context.source_loc(), $2); } -; -member_list: - // Empty - { $$ = {}; } -| member_list member - { - $$ = $1; - $$.push_back($2); - } -; alternative: identifier tuple { $$ = arena->New(context.source_loc(), $1, $2); } @@ -728,10 +731,15 @@ alternative_list_contents: declaration: function_declaration { $$ = $1; } -| CLASS identifier LEFT_CURLY_BRACE member_list RIGHT_CURLY_BRACE +| CLASS identifier LEFT_CURLY_BRACE declaration_list RIGHT_CURLY_BRACE { $$ = arena->New(context.source_loc(), $2, $4); } | CHOICE identifier LEFT_CURLY_BRACE alternative_list RIGHT_CURLY_BRACE { $$ = arena->New(context.source_loc(), $2, $4); } +| VAR variable_declaration SEMICOLON + { + $$ = arena->New(context.source_loc(), $2, + std::nullopt); + } | VAR variable_declaration EQUAL expression SEMICOLON { $$ = arena->New(context.source_loc(), $2, $4); } ; diff --git a/executable_semantics/testdata/class/bound_method.carbon b/executable_semantics/testdata/class/bound_method.carbon new file mode 100644 index 000000000000..70f1a6beede3 --- /dev/null +++ b/executable_semantics/testdata/class/bound_method.carbon @@ -0,0 +1,29 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// RUN: %{executable_semantics} %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s +// RUN: %{executable_semantics} --trace %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: %{executable_semantics} %s +// CHECK: result: 0 + +package ExecutableSemanticsTest api; + +class Point { + fn Origin() -> Point { + return {.x = 0, .y = 0}; + } + fn GetX[me: Point]() -> i32 { + return me.x; + } + var x: i32; + var y: i32; +} + +fn Main() -> i32 { + var p: Point = Point.Origin(); + var f: __Fn()-> i32 = p.GetX; + return f(); +} diff --git a/executable_semantics/testdata/class/class_function.carbon b/executable_semantics/testdata/class/class_function.carbon new file mode 100644 index 000000000000..8a191eaea2d1 --- /dev/null +++ b/executable_semantics/testdata/class/class_function.carbon @@ -0,0 +1,27 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// RUN: %{executable_semantics} %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s +// RUN: %{executable_semantics} --trace %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: %{executable_semantics} %s +// CHECK: result: 0 + +package ExecutableSemanticsTest api; + +class Point { + + fn Origin() -> Point { + return {.x = 0, .y = 0}; + } + + var x: i32; + var y: i32; +} + +fn Main() -> i32 { + var p: Point = Point.Origin(); + return p.x; +} diff --git a/executable_semantics/testdata/class/class_function_from_instance.carbon b/executable_semantics/testdata/class/class_function_from_instance.carbon new file mode 100644 index 000000000000..7ca4e329f100 --- /dev/null +++ b/executable_semantics/testdata/class/class_function_from_instance.carbon @@ -0,0 +1,27 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// RUN: %{executable_semantics} %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s +// RUN: %{executable_semantics} --trace %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: %{executable_semantics} %s +// CHECK: result: 0 + +package ExecutableSemanticsTest api; + +class Point { + + fn Origin() -> Point { + return {.x = 0, .y = 0}; + } + + var x: i32; + var y: i32; +} + +fn Main() -> i32 { + var p: Point = Point.Origin(); + return p.Origin().x; +} diff --git a/executable_semantics/testdata/class/class_function_value.carbon b/executable_semantics/testdata/class/class_function_value.carbon new file mode 100644 index 000000000000..f8befe1eca43 --- /dev/null +++ b/executable_semantics/testdata/class/class_function_value.carbon @@ -0,0 +1,27 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// RUN: %{executable_semantics} %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s +// RUN: %{executable_semantics} --trace %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: %{executable_semantics} %s +// CHECK: result: 0 + +package ExecutableSemanticsTest api; + +class Point { + + fn Origin() -> Point { + return {.x = 0, .y = 0}; + } + + var x: i32; + var y: i32; +} + +fn Main() -> i32 { + var f: __Fn()->Point = Point.Origin; + return f().x; +} diff --git a/executable_semantics/testdata/class/fail_method_from_class.carbon b/executable_semantics/testdata/class/fail_method_from_class.carbon new file mode 100644 index 000000000000..6ec4abc8e863 --- /dev/null +++ b/executable_semantics/testdata/class/fail_method_from_class.carbon @@ -0,0 +1,30 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// RUN: %{not} %{executable_semantics} %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s +// RUN: %{not} %{executable_semantics} --trace %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: %{executable_semantics} %s +// CHECK: COMPILATION ERROR: {{.*}}/executable_semantics/testdata/class/fail_method_from_class.carbon:29: GetX is not a class function + +package ExecutableSemanticsTest api; + +class Point { + + fn Origin() -> Point { + return {.x = 0, .y = 0}; + } + + fn GetX[me: Point]() -> i32 { + return me.x; + } + + var x: i32; + var y: i32; +} + +fn Main() -> i32 { + return Point.GetX(); +} diff --git a/executable_semantics/testdata/class/method.carbon b/executable_semantics/testdata/class/method.carbon new file mode 100644 index 000000000000..5b385fbebdfb --- /dev/null +++ b/executable_semantics/testdata/class/method.carbon @@ -0,0 +1,31 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// RUN: %{executable_semantics} %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s +// RUN: %{executable_semantics} --trace %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: %{executable_semantics} %s +// CHECK: result: 0 + +package ExecutableSemanticsTest api; + +class Point { + + fn Origin() -> Point { + return {.x = 0, .y = 0}; + } + + fn GetX[me: Point]() -> i32 { + return me.x; + } + + var x: i32; + var y: i32; +} + +fn Main() -> i32 { + var p: Point = Point.Origin(); + return p.GetX(); +} diff --git a/executable_semantics/testdata/class/method_call_method.carbon b/executable_semantics/testdata/class/method_call_method.carbon new file mode 100644 index 000000000000..f3a5a5258d13 --- /dev/null +++ b/executable_semantics/testdata/class/method_call_method.carbon @@ -0,0 +1,35 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// RUN: %{executable_semantics} %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s +// RUN: %{executable_semantics} --trace %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: %{executable_semantics} %s +// CHECK: result: 0 + +package ExecutableSemanticsTest api; + +class Point { + + fn Origin() -> Point { + return {.x = 0, .y = 0}; + } + + fn GetX[me: Point]() -> i32 { + return me.x; + } + + fn GetXY[me: Point]() -> (i32, i32) { + return (me.GetX(), me.y); + } + + var x: i32; + var y: i32; +} + +fn Main() -> i32 { + var p: Point = Point.Origin(); + return p.GetXY()[0]; +}