From 7cce1bd1248eb5779b9a1e34287c960e4ce4ced7 Mon Sep 17 00:00:00 2001 From: "Jeremy G. Siek" Date: Wed, 2 Mar 2022 15:58:45 -0500 Subject: [PATCH] interfaces, impls, and constrained generics (basics) (#1073) * interfaces, impls, and constrained generics (basics) * separate type checking into declare vs. type check, removing redundancy * external impls * added impl scopes to handle generics calling generics * cleanup * more cleanup * Update executable_semantics/testdata/interface/external_impl_point_vector.carbon Co-authored-by: josh11b * Update executable_semantics/testdata/interface/generic_call_generic.carbon Co-authored-by: josh11b * Update executable_semantics/testdata/interface/tuple_vector_add_scale.carbon Co-authored-by: josh11b * Update executable_semantics/testdata/interface/vector_point_add_scale.carbon Co-authored-by: josh11b * change ImplementationDeclaration to ImplDeclaration * remove impl_type_value * split NamedEntity into two * changed GetName to be a free function * adding comments * more edits to respond to review * introduce ImplBinding, remove punning on GenericBinding * new test case and some minor edits * refactor GetMember and GetField to move impl logic to interpreter * remove commennt * change EntityView to ImplBinding in FieldAccess... * move ImplBinding * review response * added example to impl_scope.h * minor edits * Update executable_semantics/interpreter/field_path.h Co-authored-by: Geoff Romer * Update executable_semantics/interpreter/value.cpp Co-authored-by: Geoff Romer * Update executable_semantics/interpreter/interpreter.cpp Co-authored-by: Geoff Romer * Update executable_semantics/ast/expression.h Co-authored-by: Geoff Romer * Update executable_semantics/ast/expression.h Co-authored-by: Geoff Romer * Update executable_semantics/ast/generic_binding.h Co-authored-by: Geoff Romer * more edits from review * review response * Update executable_semantics/ast/static_scope.h Co-authored-by: Geoff Romer * remove ImplType, renamed node_view to value_node Co-authored-by: josh11b Co-authored-by: Geoff Romer --- executable_semantics/ast/BUILD | 18 + executable_semantics/ast/README.md | 2 +- executable_semantics/ast/ast_rtti.txt | 3 + executable_semantics/ast/declaration.cpp | 44 ++ executable_semantics/ast/declaration.h | 164 ++++-- executable_semantics/ast/expression.h | 59 +- executable_semantics/ast/generic_binding.h | 139 +++++ executable_semantics/ast/pattern.h | 14 +- executable_semantics/ast/return_term.h | 10 +- executable_semantics/ast/statement.h | 12 +- executable_semantics/ast/static_scope.cpp | 10 +- executable_semantics/ast/static_scope.h | 99 ++-- executable_semantics/interpreter/BUILD | 12 +- executable_semantics/interpreter/action.cpp | 18 +- executable_semantics/interpreter/action.h | 10 +- .../interpreter/action_stack.cpp | 20 +- .../interpreter/action_stack.h | 10 +- executable_semantics/interpreter/field_path.h | 36 +- executable_semantics/interpreter/heap.cpp | 5 +- executable_semantics/interpreter/heap.h | 4 +- .../interpreter/impl_scope.cpp | 78 +++ executable_semantics/interpreter/impl_scope.h | 83 +++ .../interpreter/interpreter.cpp | 43 +- .../interpreter/interpreter.h | 1 - .../interpreter/resolve_control_flow.cpp | 17 +- .../interpreter/resolve_names.cpp | 36 +- .../interpreter/type_checker.cpp | 544 ++++++++++++------ .../interpreter/type_checker.h | 75 ++- executable_semantics/interpreter/value.cpp | 133 +++-- executable_semantics/interpreter/value.h | 84 ++- executable_semantics/syntax/lexer.lpp | 6 + executable_semantics/syntax/parser.ypp | 19 + .../testdata/basic_syntax/trace.carbon | 1 - .../global_variable/fail_init_order.carbon | 2 +- .../external_impl_point_vector.carbon | 44 ++ .../interface/fail_impl_bad_member.carbon | 43 ++ .../interface/fail_impl_missing_member.carbon | 40 ++ .../testdata/interface/fail_no_impl.carbon | 33 ++ .../interface/generic_call_generic.carbon | 45 ++ .../interface/generic_with_two_params.carbon | 59 ++ .../interface/tuple_vector_add_scale.carbon | 43 ++ .../interface/vector_point_add_scale.carbon | 43 ++ .../testdata/tuple/fail_index_var.carbon | 2 +- 43 files changed, 1722 insertions(+), 441 deletions(-) create mode 100644 executable_semantics/ast/generic_binding.h create mode 100644 executable_semantics/interpreter/impl_scope.cpp create mode 100644 executable_semantics/interpreter/impl_scope.h create mode 100644 executable_semantics/testdata/interface/external_impl_point_vector.carbon create mode 100644 executable_semantics/testdata/interface/fail_impl_bad_member.carbon create mode 100644 executable_semantics/testdata/interface/fail_impl_missing_member.carbon create mode 100644 executable_semantics/testdata/interface/fail_no_impl.carbon create mode 100644 executable_semantics/testdata/interface/generic_call_generic.carbon create mode 100644 executable_semantics/testdata/interface/generic_with_two_params.carbon create mode 100644 executable_semantics/testdata/interface/tuple_vector_add_scale.carbon create mode 100644 executable_semantics/testdata/interface/vector_point_add_scale.carbon diff --git a/executable_semantics/ast/BUILD b/executable_semantics/ast/BUILD index 951eddc24365..96228b229c8a 100644 --- a/executable_semantics/ast/BUILD +++ b/executable_semantics/ast/BUILD @@ -69,6 +69,22 @@ cc_test( ], ) +cc_library( + name = "generic_binding", + hdrs = [ + "generic_binding.h", + ], + deps = [ + ":ast_node", + ":source_location", + ":value_category", + "//common:check", + "//common:ostream", + "//executable_semantics/common:nonnull", + "@llvm-project//llvm:Support", + ], +) + cc_library( name = "declaration", srcs = ["declaration.cpp"], @@ -77,6 +93,7 @@ cc_library( ], deps = [ ":ast_node", + ":generic_binding", ":pattern", ":return_term", ":source_location", @@ -108,6 +125,7 @@ cc_library( hdrs = ["expression.h"], deps = [ ":ast_node", + ":generic_binding", ":paren_contents", ":source_location", ":static_scope", diff --git a/executable_semantics/ast/README.md b/executable_semantics/ast/README.md index 47a61e79d49a..448045e8b32b 100644 --- a/executable_semantics/ast/README.md +++ b/executable_semantics/ast/README.md @@ -40,5 +40,5 @@ inheritance, we handle these cases using a form of type erasure: we specify a notional interface that those types conform to, and then define a "view" class that behaves like a pointer to an instance of that interface. Types declare that they model an interface `Foo` by defining a public static member named -`ImplementsCarbonFoo`. See [NamedEntityView](static_scope.h) for an example of +`ImplementsCarbonFoo`. See [ValueNodeView](static_scope.h) for an example of this pattern. diff --git a/executable_semantics/ast/ast_rtti.txt b/executable_semantics/ast/ast_rtti.txt index aafaa941901c..71538e4c9401 100644 --- a/executable_semantics/ast/ast_rtti.txt +++ b/executable_semantics/ast/ast_rtti.txt @@ -14,7 +14,10 @@ abstract class Declaration : AstNode; class ClassDeclaration : Declaration; class ChoiceDeclaration : Declaration; class VariableDeclaration : Declaration; + class InterfaceDeclaration : Declaration; + class ImplDeclaration : Declaration; class GenericBinding : AstNode; +class ImplBinding : AstNode; class AlternativeSignature : AstNode; abstract class Statement : AstNode; class ExpressionStatement : Statement; diff --git a/executable_semantics/ast/declaration.cpp b/executable_semantics/ast/declaration.cpp index 0158e9a89590..d52e11fdef17 100644 --- a/executable_semantics/ast/declaration.cpp +++ b/executable_semantics/ast/declaration.cpp @@ -15,6 +15,32 @@ Declaration::~Declaration() = default; void Declaration::Print(llvm::raw_ostream& out) const { switch (kind()) { + case DeclarationKind::InterfaceDeclaration: { + const auto& iface_decl = cast(*this); + out << "interface " << iface_decl.name() << " {\n"; + for (Nonnull m : iface_decl.members()) { + out << *m; + } + out << "}\n"; + break; + } + case DeclarationKind::ImplDeclaration: { + const auto& impl_decl = cast(*this); + switch (impl_decl.kind()) { + case ImplKind::InternalImpl: + break; + case ImplKind::ExternalImpl: + out << "external "; + break; + } + out << "impl " << *impl_decl.impl_type() << " as " + << impl_decl.interface() << " {\n"; + for (Nonnull m : impl_decl.members()) { + out << *m; + } + out << "}\n"; + break; + } case DeclarationKind::FunctionDeclaration: cast(*this).PrintDepth(-1, out); break; @@ -51,6 +77,23 @@ void Declaration::Print(llvm::raw_ostream& out) const { } } +auto GetName(const Declaration& declaration) -> std::optional { + switch (declaration.kind()) { + case DeclarationKind::FunctionDeclaration: + return cast(declaration).name(); + case DeclarationKind::ClassDeclaration: + return cast(declaration).name(); + case DeclarationKind::ChoiceDeclaration: + return cast(declaration).name(); + case DeclarationKind::InterfaceDeclaration: + return cast(declaration).name(); + case DeclarationKind::VariableDeclaration: + return cast(declaration).binding().name(); + case DeclarationKind::ImplDeclaration: + return std::nullopt; + } +} + void GenericBinding::Print(llvm::raw_ostream& out) const { out << name() << ":! " << type(); } @@ -63,6 +106,7 @@ void ReturnTerm::Print(llvm::raw_ostream& out) const { out << "-> auto"; return; case ReturnKind::Expression: + CHECK(type_expression_.has_value()); out << "-> " << **type_expression_; return; } diff --git a/executable_semantics/ast/declaration.h b/executable_semantics/ast/declaration.h index fd1f060aff3d..338784a4fa7e 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/ast_node.h" +#include "executable_semantics/ast/generic_binding.h" #include "executable_semantics/ast/pattern.h" #include "executable_semantics/ast/return_term.h" #include "executable_semantics/ast/source_location.h" @@ -56,7 +57,10 @@ class Declaration : public AstNode { // Sets the static type of the declared entity. Can only be called once, // during typechecking. - void set_static_type(Nonnull type) { static_type_ = type; } + void set_static_type(Nonnull type) { + CHECK(!static_type_.has_value()); + static_type_ = type; + } // Returns whether the static type has been set. Should only be called // during typechecking: before typechecking it's guaranteed to be false, @@ -74,62 +78,9 @@ class Declaration : public AstNode { std::optional> static_type_; }; -// TODO: expand the kinds of things that can be deduced parameters. -// For now, only generic parameters are supported. -class GenericBinding : public AstNode { - public: - using ImplementsCarbonNamedEntity = void; - - GenericBinding(SourceLocation source_loc, std::string name, - Nonnull type) - : AstNode(AstNodeKind::GenericBinding, source_loc), - name_(std::move(name)), - type_(type) {} - - void Print(llvm::raw_ostream& out) const override; - - static auto classof(const AstNode* node) -> bool { - return InheritsFromGenericBinding(node->kind()); - } - - auto name() const -> const std::string& { return name_; } - auto type() const -> const Expression& { return *type_; } - auto type() -> Expression& { return *type_; } - - // The static type of the binding. Cannot be called before typechecking. - auto static_type() const -> const Value& { return **static_type_; } - - // Sets the static type of the binding. Can only be called once, during - // typechecking. - void set_static_type(Nonnull type) { static_type_ = type; } - - // Returns whether the 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 value_category() const -> ValueCategory { return ValueCategory::Let; } - auto constant_value() const -> std::optional> { - return constant_value_; - } - - // Sets the value returned by constant_value(). Can only be called once, - // during typechecking. - void set_constant_value(Nonnull value) { - CHECK(!constant_value_.has_value()); - constant_value_ = value; - } - - private: - std::string name_; - Nonnull type_; - std::optional> static_type_; - std::optional> constant_value_; -}; - class FunctionDeclaration : public Declaration { public: - using ImplementsCarbonNamedEntity = void; + using ImplementsCarbonValueNode = void; FunctionDeclaration(SourceLocation source_loc, std::string name, std::vector> deduced_params, @@ -196,7 +147,7 @@ class FunctionDeclaration : public Declaration { class ClassDeclaration : public Declaration { public: - using ImplementsCarbonNamedEntity = void; + using ImplementsCarbonValueNode = void; ClassDeclaration(SourceLocation source_loc, std::string name, std::vector> members) @@ -256,7 +207,7 @@ class AlternativeSignature : public AstNode { class ChoiceDeclaration : public Declaration { public: - using ImplementsCarbonNamedEntity = void; + using ImplementsCarbonValueNode = void; ChoiceDeclaration(SourceLocation source_loc, std::string name, std::vector> alternatives) @@ -324,6 +275,105 @@ class VariableDeclaration : public Declaration { std::optional> initializer_; }; +class InterfaceDeclaration : public Declaration { + public: + using ImplementsCarbonValueNode = void; + + InterfaceDeclaration(SourceLocation source_loc, std::string name, + Nonnull self, + std::vector> members) + : Declaration(AstNodeKind::InterfaceDeclaration, source_loc), + name_(std::move(name)), + members_(std::move(members)), + self_(self) {} + + static auto classof(const AstNode* node) -> bool { + return InheritsFromInterfaceDeclaration(node->kind()); + } + + auto name() const -> const std::string& { return name_; } + auto members() const -> llvm::ArrayRef> { + return members_; + } + auto self() const -> Nonnull { return self_; } + auto self() -> Nonnull { return self_; } + + auto value_category() const -> ValueCategory { return ValueCategory::Let; } + auto constant_value() const -> std::optional> { + return constant_value_; + } + + // Sets the value returned by constant_value(). Can only be called once, + // during typechecking. + void set_constant_value(Nonnull value) { + CHECK(!constant_value_.has_value()); + constant_value_ = value; + } + + private: + std::string name_; + std::vector> members_; + std::optional> constant_value_; + Nonnull self_; +}; + +enum class ImplKind { InternalImpl, ExternalImpl }; + +class ImplDeclaration : public Declaration { + public: + using ImplementsCarbonValueNode = void; + + ImplDeclaration(SourceLocation source_loc, ImplKind kind, + Nonnull impl_type, + Nonnull interface, + std::vector> members) + : Declaration(AstNodeKind::ImplDeclaration, source_loc), + kind_(kind), + impl_type_(impl_type), + interface_(interface), + members_(members) {} + + static auto classof(const AstNode* node) -> bool { + return InheritsFromImplDeclaration(node->kind()); + } + // Return whether this is an external or internal impl. + auto kind() const -> ImplKind { return kind_; } + // Return the type that is doing the implementing. + auto impl_type() const -> Nonnull { return impl_type_; } + // Return the interface that is being implemented. + auto interface() const -> const Expression& { return *interface_; } + auto interface() -> Expression& { return *interface_; } + void set_interface_type(Nonnull iface_type) { + interface_type_ = iface_type; + } + auto interface_type() const -> Nonnull { + return *interface_type_; + } + auto members() const -> llvm::ArrayRef> { + return members_; + } + // Return the witness table for this impl. + auto constant_value() const -> std::optional> { + return constant_value_; + } + void set_constant_value(Nonnull value) { + CHECK(!constant_value_.has_value()); + constant_value_ = value; + } + auto value_category() const -> ValueCategory { return ValueCategory::Let; } + + private: + ImplKind kind_; + Nonnull impl_type_; // TODO: make this optional + Nonnull interface_; + std::optional> interface_type_; + std::vector> members_; + std::optional> constant_value_; +}; + +// Return the name of a declaration, if it has one. +auto GetName(const Declaration&) -> std::optional; + } // namespace Carbon #endif // EXECUTABLE_SEMANTICS_AST_DECLARATION_H_ diff --git a/executable_semantics/ast/expression.h b/executable_semantics/ast/expression.h index 040322a18235..8e3046060b32 100644 --- a/executable_semantics/ast/expression.h +++ b/executable_semantics/ast/expression.h @@ -12,6 +12,7 @@ #include "common/ostream.h" #include "executable_semantics/ast/ast_node.h" +#include "executable_semantics/ast/generic_binding.h" #include "executable_semantics/ast/paren_contents.h" #include "executable_semantics/ast/source_location.h" #include "executable_semantics/ast/static_scope.h" @@ -23,6 +24,7 @@ namespace Carbon { class Value; +class VariableType; class Expression : public AstNode { public: @@ -45,12 +47,10 @@ class Expression : public AstNode { // Sets the static type of this expression. Can only be called once, during // typechecking. - void set_static_type(Nonnull type) { static_type_ = type; } - - // Returns whether the 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(); } + void set_static_type(Nonnull type) { + CHECK(!static_type_.has_value()); + static_type_ = type; + } // The value category of this expression. Cannot be called before // typechecking. @@ -123,20 +123,20 @@ class IdentifierExpression : public Expression { auto name() const -> const std::string& { return name_; } - // Returns the NamedEntityView this identifier refers to. Cannot be called + // Returns the ValueNodeView this identifier refers to. Cannot be called // before name resolution. - auto named_entity() const -> const NamedEntityView& { return *named_entity_; } + auto value_node() const -> const ValueNodeView& { return *value_node_; } - // Sets the value returned by named_entity. Can be called only once, + // Sets the value returned by value_node. Can be called only once, // during name resolution. - void set_named_entity(NamedEntityView named_entity) { - CHECK(!named_entity_.has_value()); - named_entity_ = std::move(named_entity); + void set_value_node(ValueNodeView value_node) { + CHECK(!value_node_.has_value()); + value_node_ = std::move(value_node); } private: std::string name_; - std::optional named_entity_; + std::optional value_node_; }; class FieldAccessExpression : public Expression { @@ -156,9 +156,23 @@ class FieldAccessExpression : public Expression { auto aggregate() -> Expression& { return *aggregate_; } auto field() const -> const std::string& { return field_; } + // If `aggregate` has a generic type, returns the `ImplBinding` that + // identifies its witness table. Otherwise, returns `std::nullopt`. Should not + // be called before typechecking. + auto impl() const -> std::optional> { + return impl_; + } + + // Can only be called once, during typechecking. + void set_impl(Nonnull impl) { + CHECK(!impl_.has_value()); + impl_ = impl; + } + private: Nonnull aggregate_; std::string field_; + std::optional> impl_; }; class IndexExpression : public Expression { @@ -340,6 +354,8 @@ class PrimitiveOperatorExpression : public Expression { std::vector> arguments_; }; +class ImplBinding; + class CallExpression : public Expression { public: explicit CallExpression(SourceLocation source_loc, @@ -358,9 +374,26 @@ class CallExpression : public Expression { auto argument() const -> const Expression& { return *argument_; } auto argument() -> Expression& { return *argument_; } + // Maps each of `function`'s generic parameters to the AST node + // that identifies the witness table for the corresponding argument. + // Should not be called before typechecking, or if `function` is not + // a generic function. + auto impls() const + -> const std::map, ValueNodeView>& { + return impls_; + } + + // Can only be called once, during typechecking. + void set_impls( + const std::map, ValueNodeView>& impls) { + CHECK(impls_.empty()); + impls_ = impls; + } + private: Nonnull function_; Nonnull argument_; + std::map, ValueNodeView> impls_; }; class FunctionTypeLiteral : public Expression { diff --git a/executable_semantics/ast/generic_binding.h b/executable_semantics/ast/generic_binding.h new file mode 100644 index 000000000000..a67a94551fcb --- /dev/null +++ b/executable_semantics/ast/generic_binding.h @@ -0,0 +1,139 @@ +// 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_GENERIC_BINDING_H_ +#define EXECUTABLE_SEMANTICS_AST_GENERIC_BINDING_H_ + +#include + +#include "common/check.h" +#include "common/ostream.h" +#include "executable_semantics/ast/ast_node.h" +#include "executable_semantics/ast/value_category.h" + +namespace Carbon { + +class Value; +class Expression; +class ImplBinding; + +// TODO: expand the kinds of things that can be deduced parameters. +// For now, only generic parameters are supported. +class GenericBinding : public AstNode { + public: + using ImplementsCarbonValueNode = void; + + GenericBinding(SourceLocation source_loc, std::string name, + Nonnull type) + : AstNode(AstNodeKind::GenericBinding, source_loc), + name_(std::move(name)), + type_(type) {} + + void Print(llvm::raw_ostream& out) const override; + + static auto classof(const AstNode* node) -> bool { + return InheritsFromGenericBinding(node->kind()); + } + + auto name() const -> const std::string& { return name_; } + auto type() const -> const Expression& { return *type_; } + auto type() -> Expression& { return *type_; } + + // The static type of the binding. Cannot be called before typechecking. + auto static_type() const -> const Value& { return **static_type_; } + + // Sets the static type of the binding. Can only be called once, during + // typechecking. + void set_static_type(Nonnull type) { + CHECK(!static_type_.has_value()); + static_type_ = type; + } + + auto value_category() const -> ValueCategory { return ValueCategory::Let; } + auto constant_value() const -> std::optional> { + return constant_value_; + } + + // Sets the value returned by constant_value(). Can only be called once, + // during typechecking. + void set_constant_value(Nonnull value) { + CHECK(!constant_value_.has_value()); + constant_value_ = value; + } + + // The impl binding associated with this type variable. + auto impl_binding() const -> std::optional> { + return impl_binding_; + } + // Set the impl binding. + void set_impl_binding(Nonnull binding) { + CHECK(!impl_binding_.has_value()); + impl_binding_ = binding; + } + + private: + std::string name_; + Nonnull type_; + std::optional> static_type_; + std::optional> constant_value_; + std::optional> impl_binding_; +}; + +using BindingMap = + std::map, Nonnull>; + +// The run-time counterpart of a `GenericBinding`. +// +// Once a generic binding has been declared, it can be used +// in two different ways: as a compile-time constant with a +// symbolic value (such as a `VariableType`), or as a run-time +// variable with a concrete value that is stored on the stack. +// An `ImplBinding` is used in contexts where the second +// interpretation is intended. +class ImplBinding : public AstNode { + public: + using ImplementsCarbonValueNode = void; + + ImplBinding(SourceLocation source_loc, + Nonnull type_var, + Nonnull iface) + : AstNode(AstNodeKind::ImplBinding, source_loc), + type_var_(type_var), + iface_(iface) {} + + static auto classof(const AstNode* node) -> bool { + return InheritsFromImplBinding(node->kind()); + } + void Print(llvm::raw_ostream& out) const override; + + // The binding for the type variable. + auto type_var() const -> Nonnull { return type_var_; } + // The interface being implemented. + auto interface() const -> Nonnull { return iface_; } + + // Required for the the ValueNode interface + auto constant_value() const -> std::optional> { + return std::nullopt; + } + + // The static type of the impl. Cannot be called before typechecking. + auto static_type() const -> const Value& { return **static_type_; } + + // Sets the static type of the impl. Can only be called once, during + // typechecking. + void set_static_type(Nonnull type) { + CHECK(!static_type_.has_value()); + static_type_ = type; + } + auto value_category() const -> ValueCategory { return ValueCategory::Let; } + + private: + Nonnull type_var_; + Nonnull iface_; + std::optional> static_type_; +}; + +} // namespace Carbon + +#endif // EXECUTABLE_SEMANTICS_AST_GENERIC_BINDING_H_ diff --git a/executable_semantics/ast/pattern.h b/executable_semantics/ast/pattern.h index 21d034961549..08c33166c830 100644 --- a/executable_semantics/ast/pattern.h +++ b/executable_semantics/ast/pattern.h @@ -54,12 +54,10 @@ class Pattern : public AstNode { // Sets the static type of this expression. Can only be called once, during // typechecking. - void set_static_type(Nonnull type) { static_type_ = type; } - - // Returns whether the 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(); } + void set_static_type(Nonnull type) { + CHECK(!static_type_.has_value()); + static_type_ = type; + } // The value of this pattern. Cannot be called before typechecking. // TODO rename to avoid confusion with BindingPattern::constant_value @@ -101,7 +99,7 @@ class AutoPattern : public Pattern { // a name to it. class BindingPattern : public Pattern { public: - using ImplementsCarbonNamedEntity = void; + using ImplementsCarbonValueNode = void; BindingPattern(SourceLocation source_loc, std::string name, Nonnull type) @@ -115,7 +113,7 @@ class BindingPattern : public Pattern { // The name this pattern binds, if any. If equal to AnonymousName, indicates // that this BindingPattern does not bind a name, which in turn means it - // should not be used as a NamedEntity. + // should not be used as a ValueNode. auto name() const -> const std::string& { return name_; } // The pattern specifying the type of values that this pattern matches. diff --git a/executable_semantics/ast/return_term.h b/executable_semantics/ast/return_term.h index 9401f106de37..e6cf613ac3e5 100644 --- a/executable_semantics/ast/return_term.h +++ b/executable_semantics/ast/return_term.h @@ -65,12 +65,10 @@ class ReturnTerm { // 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(); } + void set_static_type(Nonnull type) { + CHECK(!static_type_.has_value()); + static_type_ = type; + } auto source_loc() const -> SourceLocation { return source_loc_; } diff --git a/executable_semantics/ast/statement.h b/executable_semantics/ast/statement.h index 508ed1113245..de34d89ee3ff 100644 --- a/executable_semantics/ast/statement.h +++ b/executable_semantics/ast/statement.h @@ -314,7 +314,7 @@ class Match : public Statement { // } class Continuation : public Statement { public: - using ImplementsCarbonNamedEntity = void; + using ImplementsCarbonValueNode = void; Continuation(SourceLocation source_loc, std::string name, Nonnull body) @@ -338,12 +338,10 @@ class Continuation : public Statement { // Sets the static type of the continuation. Can only be called once, // during typechecking. - void set_static_type(Nonnull type) { static_type_ = type; } - - // Returns whether the 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(); } + void set_static_type(Nonnull type) { + CHECK(!static_type_.has_value()); + static_type_ = type; + } auto value_category() const -> ValueCategory { return ValueCategory::Var; } auto constant_value() const -> std::optional> { diff --git a/executable_semantics/ast/static_scope.cpp b/executable_semantics/ast/static_scope.cpp index 28cbfbdae644..f10458f11c79 100644 --- a/executable_semantics/ast/static_scope.cpp +++ b/executable_semantics/ast/static_scope.cpp @@ -8,7 +8,7 @@ namespace Carbon { -void StaticScope::Add(std::string name, NamedEntityView entity) { +void StaticScope::Add(std::string name, ValueNodeView entity) { auto [it, success] = declared_names_.insert({name, entity}); if (!success && it->second != entity) { FATAL_COMPILATION_ERROR(entity.base().source_loc()) @@ -18,8 +18,8 @@ void StaticScope::Add(std::string name, NamedEntityView entity) { } auto StaticScope::Resolve(const std::string& name, - SourceLocation source_loc) const -> NamedEntityView { - std::optional result = TryResolve(name, source_loc); + SourceLocation source_loc) const -> ValueNodeView { + std::optional result = TryResolve(name, source_loc); if (!result.has_value()) { FATAL_COMPILATION_ERROR(source_loc) << "could not resolve '" << name << "'"; } @@ -28,12 +28,12 @@ auto StaticScope::Resolve(const std::string& name, auto StaticScope::TryResolve(const std::string& name, SourceLocation source_loc) const - -> std::optional { + -> std::optional { auto it = declared_names_.find(name); if (it != declared_names_.end()) { return it->second; } - std::optional result; + std::optional result; for (Nonnull parent : parent_scopes_) { auto parent_result = parent->TryResolve(name, source_loc); if (parent_result.has_value() && result.has_value() && diff --git a/executable_semantics/ast/static_scope.h b/executable_semantics/ast/static_scope.h index fd9918ecd25b..dab72c2d2d89 100644 --- a/executable_semantics/ast/static_scope.h +++ b/executable_semantics/ast/static_scope.h @@ -21,78 +21,78 @@ namespace Carbon { class Value; -// The placeholder name exposed by anonymous NamedEntities. +// The placeholder name exposed by anonymous ValueNodes. static constexpr std::string_view AnonymousName = "_"; -// True if NodeType::ImplementsCarbonNamedEntity is valid and names a type, -// indicating that NodeType implements the NamedEntity interface, which means -// it must define the following methods, with contracts as documented. +// ImplementsValueNode is true if NodeType::ImplementsCarbonValueNode +// is valid and names a type, indicating that NodeType implements the +// ValueNode interface, defined below. + +template +static constexpr bool ImplementsValueNode = false; + /* + ValueNode is an interface implemented by AstNodes that can be associated + with a value, such as declarations and bindings. The interface consists of + the following methods: + // Returns the static type of an IdentifierExpression that names *this. auto static_type() const -> const Value&; // Returns the value category of an IdentifierExpression that names *this. auto value_category() const -> ValueCategory; - // Returns the name of an IdentifierExpression that names *this. If *this - // is anonymous, returns AnonymousName. - auto name() const -> std::string_view; + // Print the node for diagnostic or tracing purposes. + void Print(llvm::raw_ostream& out) const; + - // If *this names a compile-time constant whose value is known, returns that - // value. Otherwise returns std::nullopt. - auto constant_value() const -> std::optional>; */ -// NodeType must be derived from AstNode. -// // TODO: consider turning the above documentation into real code, as sketched // at https://godbolt.org/z/186oEozhc -template -static constexpr bool ImplementsNamedEntity = false; template static constexpr bool - ImplementsNamedEntity = true; + ImplementsValueNode = true; -// Non-owning type-erased wrapper around a const NodeType* `node`, where -// NodeType implements the NamedEntity interface. -class NamedEntityView { +class ValueNodeView { public: - // REQUIRES: node->name() != AnonymousName template >> + typename = std::enable_if_t>> // NOLINTNEXTLINE(google-explicit-constructor) - NamedEntityView(Nonnull node) + ValueNodeView(Nonnull node) // Type-erase NodeType, retaining a pointer to the base class AstNode // and using std::function to encapsulate the ability to call // the derived class's methods. : base_(node), - name_([](const AstNode& base) -> std::string_view { - return llvm::cast(base).name(); + constant_value_( + [](const AstNode& base) -> std::optional> { + return llvm::cast(base).constant_value(); + }), + print_([](const AstNode& base, llvm::raw_ostream& out) -> void { + // TODO: change this to print a summary of the node + return llvm::cast(base).Print(out); }), static_type_([](const AstNode& base) -> const Value& { return llvm::cast(base).static_type(); }), value_category_([](const AstNode& base) -> ValueCategory { return llvm::cast(base).value_category(); - }), - constant_value_( - [](const AstNode& base) -> std::optional> { - return llvm::cast(base).constant_value(); - }) { - CHECK(node->name() != AnonymousName) - << "Entity with no name used as NamedEntity: " << *node; - } + }) {} - NamedEntityView(const NamedEntityView&) = default; - NamedEntityView(NamedEntityView&&) = default; - auto operator=(const NamedEntityView&) -> NamedEntityView& = default; - auto operator=(NamedEntityView&&) -> NamedEntityView& = default; + ValueNodeView(const ValueNodeView&) = default; + ValueNodeView(ValueNodeView&&) = default; + auto operator=(const ValueNodeView&) -> ValueNodeView& = default; + auto operator=(ValueNodeView&&) -> ValueNodeView& = default; // Returns `node` as an instance of the base class AstNode. auto base() const -> const AstNode& { return *base_; } - // Returns node->name() - auto name() const -> std::string_view { return name_(*base_); } + // Returns node->constant_value() + auto constant_value() const -> std::optional> { + return constant_value_(*base_); + } + + void Print(llvm::raw_ostream& out) const { print_(*base_, out); } // Returns node->static_type() auto static_type() const -> const Value& { return static_type_(*base_); } @@ -102,33 +102,28 @@ class NamedEntityView { return value_category_(*base_); } - // Returns node->constant_value() - auto constant_value() const -> std::optional> { - return constant_value_(*base_); - } - - friend auto operator==(const NamedEntityView& lhs, const NamedEntityView& rhs) + friend auto operator==(const ValueNodeView& lhs, const ValueNodeView& rhs) -> bool { return lhs.base_ == rhs.base_; } - friend auto operator!=(const NamedEntityView& lhs, const NamedEntityView& rhs) + friend auto operator!=(const ValueNodeView& lhs, const ValueNodeView& rhs) -> bool { return lhs.base_ != rhs.base_; } - friend auto operator<(const NamedEntityView& lhs, const NamedEntityView& rhs) + friend auto operator<(const ValueNodeView& lhs, const ValueNodeView& rhs) -> bool { return std::less<>()(lhs.base_, rhs.base_); } private: Nonnull base_; - std::function name_; - std::function static_type_; - std::function value_category_; std::function>(const AstNode&)> constant_value_; + std::function print_; + std::function static_type_; + std::function value_category_; }; // Maps the names visible in a given scope to the entities they name. @@ -138,7 +133,7 @@ class StaticScope { public: // Defines `name` to be `entity` in this scope, or reports a compilation error // if `name` is already defined to be a different entity in this scope. - void Add(std::string name, NamedEntityView entity); + void Add(std::string name, ValueNodeView entity); // Make `parent` a parent of this scope. // REQUIRES: `parent` is not already a parent of this scope. @@ -150,17 +145,17 @@ class StaticScope { // scope, or reports a compilation error at `source_loc` there isn't exactly // one such definition. auto Resolve(const std::string& name, SourceLocation source_loc) const - -> NamedEntityView; + -> ValueNodeView; private: // Equivalent to Resolve, but returns `nullopt` instead of raising an error // if no definition can be found. Still raises a compilation error if more // than one definition is found. auto TryResolve(const std::string& name, SourceLocation source_loc) const - -> std::optional; + -> std::optional; // Maps locally declared names to their entities. - std::unordered_map declared_names_; + std::unordered_map declared_names_; // A list of scopes used for name lookup within this scope. std::vector> parent_scopes_; diff --git a/executable_semantics/interpreter/BUILD b/executable_semantics/interpreter/BUILD index 393ac25cba9c..45c0fe1afddb 100644 --- a/executable_semantics/interpreter/BUILD +++ b/executable_semantics/interpreter/BUILD @@ -85,6 +85,8 @@ cc_library( hdrs = ["field_path.h"], deps = [ "//common:ostream", + "//executable_semantics/ast", + "//executable_semantics/ast:static_scope", "@llvm-project//llvm:Support", ], ) @@ -183,8 +185,14 @@ cc_library( cc_library( name = "type_checker", - srcs = ["type_checker.cpp"], - hdrs = ["type_checker.h"], + srcs = [ + "impl_scope.cpp", + "type_checker.cpp", + ], + hdrs = [ + "impl_scope.h", + "type_checker.h", + ], deps = [ ":action_and_value", ":dictionary", diff --git a/executable_semantics/interpreter/action.cpp b/executable_semantics/interpreter/action.cpp index a341d1f6b8c2..f3132224089f 100644 --- a/executable_semantics/interpreter/action.cpp +++ b/executable_semantics/interpreter/action.cpp @@ -44,20 +44,20 @@ RuntimeScope::~RuntimeScope() { void RuntimeScope::Print(llvm::raw_ostream& out) const { out << "{"; llvm::ListSeparator sep; - for (const auto& [named_entity, value] : locals_) { - out << sep << named_entity.name() << ": " << *value; + for (const auto& [value_node, value] : locals_) { + out << sep << value_node.base() << ": " << *value; } out << "}"; } -void RuntimeScope::Initialize(NamedEntityView named_entity, +void RuntimeScope::Initialize(ValueNodeView value_node, Nonnull value) { - CHECK(!named_entity.constant_value().has_value()); + CHECK(!value_node.constant_value().has_value()); CHECK(value->kind() != Value::Kind::LValue); allocations_.push_back(heap_->AllocateValue(value)); auto [it, success] = locals_.insert( - {named_entity, heap_->arena().New(Address(allocations_.back()))}); - CHECK(success) << "Duplicate definition of " << named_entity.name(); + {value_node, heap_->arena().New(Address(allocations_.back()))}); + CHECK(success) << "Duplicate definition of " << value_node.base(); } void RuntimeScope::Merge(RuntimeScope other) { @@ -65,15 +65,15 @@ void RuntimeScope::Merge(RuntimeScope other) { locals_.merge(other.locals_); CHECK(other.locals_.empty()) << "Duplicate definition of " << other.locals_.size() - << " names, including " << other.locals_.begin()->first.name(); + << " names, including " << other.locals_.begin()->first.base(); allocations_.insert(allocations_.end(), other.allocations_.begin(), other.allocations_.end()); other.allocations_.clear(); } -auto RuntimeScope::Get(NamedEntityView named_entity) const +auto RuntimeScope::Get(ValueNodeView value_node) const -> std::optional> { - auto it = locals_.find(named_entity); + auto it = locals_.find(value_node); if (it != locals_.end()) { return it->second; } else { diff --git a/executable_semantics/interpreter/action.h b/executable_semantics/interpreter/action.h index abfacd48065c..cfa7296cce21 100644 --- a/executable_semantics/interpreter/action.h +++ b/executable_semantics/interpreter/action.h @@ -45,21 +45,21 @@ class RuntimeScope { void Print(llvm::raw_ostream& out) const; LLVM_DUMP_METHOD void Dump() const { Print(llvm::errs()); } - // Allocates storage for `named_entity` in `heap`, and initializes it with + // Allocates storage for `value_node` in `heap`, and initializes it with // `value`. - void Initialize(NamedEntityView named_entity, Nonnull value); + void Initialize(ValueNodeView value_node, Nonnull value); // Transfers the names and allocations from `other` into *this. The two // scopes must not define the same name, and must be backed by the same Heap. void Merge(RuntimeScope other); - // Returns the local storage for named_entity, if it has storage local to + // Returns the local storage for value_node, if it has storage local to // this scope. - auto Get(NamedEntityView named_entity) const + auto Get(ValueNodeView value_node) const -> std::optional>; private: - std::map> locals_; + std::map> locals_; std::vector allocations_; Nonnull heap_; }; diff --git a/executable_semantics/interpreter/action_stack.cpp b/executable_semantics/interpreter/action_stack.cpp index 5b4ed82926ec..fb10f4475b59 100644 --- a/executable_semantics/interpreter/action_stack.cpp +++ b/executable_semantics/interpreter/action_stack.cpp @@ -36,47 +36,47 @@ void ActionStack::Start(std::unique_ptr action) { todo_.Push(std::move(action)); } -void ActionStack::Initialize(NamedEntityView named_entity, +void ActionStack::Initialize(ValueNodeView value_node, Nonnull value) { for (const std::unique_ptr& action : todo_) { if (action->scope().has_value()) { - action->scope()->Initialize(named_entity, value); + action->scope()->Initialize(value_node, value); return; } } - globals_->Initialize(named_entity, value); + globals_->Initialize(value_node, value); } -auto ActionStack::ValueOfName(NamedEntityView named_entity, +auto ActionStack::ValueOfNode(ValueNodeView value_node, SourceLocation source_loc) const -> Nonnull { if (std::optional> constant_value = - named_entity.constant_value(); + value_node.constant_value(); constant_value.has_value()) { return *constant_value; } for (const std::unique_ptr& action : todo_) { - // TODO: have static name resolution identify the scope of named_entity + // TODO: have static name resolution identify the scope of value_node // as an AstNode, and then perform lookup _only_ on the Action associated // with that node. This will help keep unwanted dynamic-scoping behavior // from sneaking in. if (action->scope().has_value()) { std::optional> result = - action->scope()->Get(named_entity); + action->scope()->Get(value_node); if (result.has_value()) { return *result; } } } if (globals_.has_value()) { - std::optional> result = globals_->Get(named_entity); + std::optional> result = globals_->Get(value_node); if (result.has_value()) { return *result; } } - // TODO: Move these errors to compile time and explain them more clearly. + // TODO: Move these errors to name resolution and explain them more clearly. FATAL_RUNTIME_ERROR(source_loc) - << "could not find `" << named_entity.name() << "`"; + << "could not find `" << value_node.base() << "`"; } void ActionStack::MergeScope(RuntimeScope scope) { diff --git a/executable_semantics/interpreter/action_stack.h b/executable_semantics/interpreter/action_stack.h index 85852068849b..228ca595e660 100644 --- a/executable_semantics/interpreter/action_stack.h +++ b/executable_semantics/interpreter/action_stack.h @@ -43,13 +43,13 @@ class ActionStack { // ScopeAction. auto CurrentAction() -> Action& { return *todo_.Top(); } - // Allocates storage for `named_entity`, and initializes it to `value`. - void Initialize(NamedEntityView named_entity, Nonnull value); + // Allocates storage for `value_node`, and initializes it to `value`. + void Initialize(ValueNodeView value_node, Nonnull value); - // Returns the value bound to `named_entity`. If `named_entity` is a local + // Returns the value bound to `value_node`. If `value_node` is a local // variable, this will be an LValue. - auto ValueOfName(NamedEntityView named_entity, - SourceLocation source_loc) const -> Nonnull; + auto ValueOfNode(ValueNodeView value_node, SourceLocation source_loc) const + -> Nonnull; // Merges `scope` into the innermost scope currently on the stack. void MergeScope(RuntimeScope scope); diff --git a/executable_semantics/interpreter/field_path.h b/executable_semantics/interpreter/field_path.h index 5df4b091947b..8f89ce01e32b 100644 --- a/executable_semantics/interpreter/field_path.h +++ b/executable_semantics/interpreter/field_path.h @@ -9,10 +9,13 @@ #include #include "common/ostream.h" +#include "executable_semantics/ast/static_scope.h" #include "llvm/Support/Compiler.h" namespace Carbon { +class Witness; + // Given some initial Value, a FieldPath identifies a sub-Value within it, // in much the same way that a file path identifies a file within some // directory. FieldPaths are relative rather than absolute: the initial @@ -29,8 +32,33 @@ class FieldPath { // Constructs an empty FieldPath. FieldPath() = default; + // A single component of the FieldPath, which is typically the name + // of a field. However, inside a generic, when there is a field + // access on something of a generic type, e.g., `T`, then we also + // need `witness`, a pointer to the witness table containing that field. + class Component { + public: + explicit Component(std::string name) : name_(std::move(name)) {} + Component(std::string name, std::optional> witness) + : name_(std::move(name)), witness_(witness) {} + + auto name() const -> const std::string& { return name_; } + + auto witness() const -> std::optional> { + return witness_; + } + + void Print(llvm::raw_ostream& out) const { out << name_; } + + private: + std::string name_; + std::optional> witness_; + }; + // Constructs a FieldPath consisting of a single step. - explicit FieldPath(std::string name) : components_({std::move(name)}) {} + explicit FieldPath(std::string name) + : components_({Component(std::move(name))}) {} + explicit FieldPath(const Component& f) : components_({f}) {} FieldPath(const FieldPath&) = default; FieldPath(FieldPath&&) = default; @@ -42,11 +70,11 @@ class FieldPath { // Appends `name` to the end of *this. auto Append(std::string name) -> void { - components_.push_back(std::move(name)); + components_.push_back(Component(std::move(name))); } void Print(llvm::raw_ostream& out) const { - for (const std::string& component : components_) { + for (const Component& component : components_) { out << "." << component; } } @@ -58,7 +86,7 @@ class FieldPath { // another Value, so its implementation details are tied to the implementation // details of Value. friend class Value; - std::vector components_; + std::vector components_; }; } // namespace Carbon diff --git a/executable_semantics/interpreter/heap.cpp b/executable_semantics/interpreter/heap.cpp index 387e11962382..f1b33641e1b0 100644 --- a/executable_semantics/interpreter/heap.cpp +++ b/executable_semantics/interpreter/heap.cpp @@ -20,7 +20,7 @@ auto Heap::AllocateValue(Nonnull v) -> AllocationId { return a; } -auto Heap::Read(const Address& a, SourceLocation source_loc) +auto Heap::Read(const Address& a, SourceLocation source_loc) const -> Nonnull { this->CheckAlive(a.allocation_, source_loc); return values_[a.allocation_.index_]->GetField(arena_, a.field_path_, @@ -34,7 +34,8 @@ void Heap::Write(const Address& a, Nonnull v, arena_, a.field_path_, v, source_loc); } -void Heap::CheckAlive(AllocationId allocation, SourceLocation source_loc) { +void Heap::CheckAlive(AllocationId allocation, + SourceLocation source_loc) const { if (!alive_[allocation.index_]) { FATAL_RUNTIME_ERROR(source_loc) << "undefined behavior: access to dead value " diff --git a/executable_semantics/interpreter/heap.h b/executable_semantics/interpreter/heap.h index 3647c8858c93..c9bcbee64f35 100644 --- a/executable_semantics/interpreter/heap.h +++ b/executable_semantics/interpreter/heap.h @@ -27,7 +27,7 @@ class Heap : public HeapAllocationInterface { // Returns the value at the given address in the heap after // checking that it is alive. - auto Read(const Address& a, SourceLocation source_loc) + auto Read(const Address& a, SourceLocation source_loc) const -> Nonnull; // Writes the given value at the address in the heap after @@ -50,7 +50,7 @@ class Heap : public HeapAllocationInterface { private: // Signal an error if the allocation is no longer alive. - void CheckAlive(AllocationId allocation, SourceLocation source_loc); + void CheckAlive(AllocationId allocation, SourceLocation source_loc) const; Nonnull arena_; std::vector> values_; diff --git a/executable_semantics/interpreter/impl_scope.cpp b/executable_semantics/interpreter/impl_scope.cpp new file mode 100644 index 000000000000..b244927746ae --- /dev/null +++ b/executable_semantics/interpreter/impl_scope.cpp @@ -0,0 +1,78 @@ +// 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/interpreter/impl_scope.h" + +#include "executable_semantics/common/error.h" +#include "executable_semantics/interpreter/value.h" +#include "llvm/Support/Casting.h" + +using llvm::cast; + +namespace Carbon { + +void ImplScope::Add(Nonnull iface, Nonnull type, + ValueNodeView impl) { + impls_.push_back({.interface = iface, .type = type, .impl = impl}); +} + +void ImplScope::AddParent(Nonnull parent) { + parent_scopes_.push_back(parent); +} + +auto ImplScope::Resolve(Nonnull iface_type, + Nonnull type, + SourceLocation source_loc) const -> ValueNodeView { + std::optional result = + TryResolve(iface_type, type, source_loc); + if (!result.has_value()) { + FATAL_COMPILATION_ERROR(source_loc) << "could not find implementation of " + << *iface_type << " for " << *type; + } + return *result; +} + +auto ImplScope::TryResolve(Nonnull iface_type, + Nonnull type, + SourceLocation source_loc) const + -> std::optional { + std::optional result = + ResolveHere(iface_type, type, source_loc); + if (result.has_value()) { + return result; + } + for (Nonnull parent : parent_scopes_) { + auto parent_result = parent->TryResolve(iface_type, type, source_loc); + if (parent_result.has_value() && result.has_value() && + *parent_result != *result) { + FATAL_COMPILATION_ERROR(source_loc) + << "ambiguous implementations of " << *iface_type << " for " << *type; + } + result = parent_result; + } + return result; +} + +auto ImplScope::ResolveHere(Nonnull iface_type, + Nonnull impl_type, + SourceLocation source_loc) const + -> std::optional { + switch (iface_type->kind()) { + case Value::Kind::InterfaceType: { + const auto& iface = cast(*iface_type); + for (const Impl& impl : impls_) { + if (TypeEqual(&iface, impl.interface) && + TypeEqual(impl_type, impl.type)) { + return impl.impl; + } + } + return std::nullopt; + } + default: + FATAL() << "expected an interface, not " << *iface_type; + break; + } +} + +} // namespace Carbon diff --git a/executable_semantics/interpreter/impl_scope.h b/executable_semantics/interpreter/impl_scope.h new file mode 100644 index 000000000000..1b8e607e7f44 --- /dev/null +++ b/executable_semantics/interpreter/impl_scope.h @@ -0,0 +1,83 @@ +// 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_IMPL_SCOPE_H_ +#define EXECUTABLE_SEMANTICS_AST_IMPL_SCOPE_H_ + +#include "executable_semantics/ast/declaration.h" + +namespace Carbon { + +class Value; + +// The `ImplScope` class is responsible for mapping a type and +// interface to the location of the witness table for the `impl` for +// that type and interface. A scope may have parent scopes, whose +// impls will also be visible in the child scope. +// +// There is typically one instance of `ImplScope` class per scope +// because the impls that are visible for a given type and interface +// can vary from scope to scope. For example, consider the `bar` and +// `baz` methods in the following class C and nested class D. +// +// class C(U:! Type, T:! Type) { +// class D(V:! Type where U is Fooable(T)) { +// fn bar[me: Self](x: U, y : T) -> T{ +// return x.foo(y) +// } +// } +// fn baz[me: Self](x: U, y : T) -> T { +// return x.foo(y); +// } +// } +// +// The call to `x.foo` in `bar` is valid because the `U is Fooable(T)` +// impl is visible in the body of `bar`. In contrast, the call to +// `x.foo` in `baz` is not valid because there is no visible impl for +// `U` and `Fooable` in that scope. +class ImplScope { + public: + // Associates `iface` and `type` with the `impl` in this scope. + void Add(Nonnull iface, Nonnull type, + ValueNodeView impl); + + // Make `parent` a parent of this scope. + // REQUIRES: `parent` is not already a parent of this scope. + void AddParent(Nonnull parent); + + // Returns the associated impl for the given `iface` and `type` in + // the ancestor graph of this scope, or reports a compilation error + // at `source_loc` there isn't exactly one matching impl. + auto Resolve(Nonnull iface, Nonnull type, + SourceLocation source_loc) const -> ValueNodeView; + + private: + auto TryResolve(Nonnull iface_type, Nonnull type, + SourceLocation source_loc) const + -> std::optional; + auto ResolveHere(Nonnull iface_type, + Nonnull impl_type, + SourceLocation source_loc) const + -> std::optional; + + // The `Impl` struct is a key-value pair where the key is the + // combination of a type and an interface, e.g., `List` and `Container`, + // and the value is the result of statically resolving to the `impl` + // for `List` as `Container`, which is an `ValueNodeView`. The generality + // of `ValueNodeView` is needed (not just `ImplDeclaration`) because + // inside a generic, we need to map, e.g., from `T` and `Container` to the + // witness table that is passed into the generic. + struct Impl { + Nonnull interface; + Nonnull type; + ValueNodeView impl; + }; + + std::vector impls_; + std::vector> parent_scopes_; +}; + +} // namespace Carbon + +#endif // EXECUTABLE_SEMANTICS_AST_IMPL_SCOPE_H_ diff --git a/executable_semantics/interpreter/interpreter.cpp b/executable_semantics/interpreter/interpreter.cpp index 45b246813a44..468ce9cb579a 100644 --- a/executable_semantics/interpreter/interpreter.cpp +++ b/executable_semantics/interpreter/interpreter.cpp @@ -17,6 +17,7 @@ #include "executable_semantics/common/arena.h" #include "executable_semantics/common/error.h" #include "executable_semantics/interpreter/action.h" +#include "executable_semantics/interpreter/action_stack.h" #include "executable_semantics/interpreter/stack.h" #include "llvm/ADT/StringExtras.h" #include "llvm/Support/Casting.h" @@ -183,8 +184,8 @@ auto PatternMatch(Nonnull p, Nonnull v, << "Name bindings are not supported in this context"; } const auto& placeholder = cast(*p); - if (placeholder.named_entity().has_value()) { - (*bindings)->Initialize(*placeholder.named_entity(), v); + if (placeholder.value_node().has_value()) { + (*bindings)->Initialize(*placeholder.value_node(), v); } return true; } @@ -275,8 +276,8 @@ void Interpreter::StepLvalue() { case ExpressionKind::IdentifierExpression: { // { {x :: C, E, F} :: S, H} // -> { {E(x) :: C, E, F} :: S, H} - Nonnull value = todo_.ValueOfName( - cast(exp).named_entity(), exp.source_loc()); + Nonnull value = todo_.ValueOfNode( + cast(exp).value_node(), exp.source_loc()); CHECK(isa(value)) << *value; return todo_.FinishAction(value); } @@ -371,6 +372,8 @@ auto Interpreter::Convert(Nonnull value, case Value::Kind::AutoType: case Value::Kind::StructType: case Value::Kind::NominalClassType: + case Value::Kind::InterfaceType: + case Value::Kind::Witness: case Value::Kind::ChoiceType: case Value::Kind::ContinuationType: case Value::Kind::VariableType: @@ -380,6 +383,7 @@ auto Interpreter::Convert(Nonnull value, case Value::Kind::StringType: case Value::Kind::StringValue: case Value::Kind::TypeOfClassType: + case Value::Kind::TypeOfInterfaceType: case Value::Kind::TypeOfChoiceType: // TODO: add `CHECK(TypeEqual(type, value->dynamic_type()))`, once we // have Value::dynamic_type. @@ -497,8 +501,18 @@ void Interpreter::StepExp() { } else { // { { v :: [].f :: C, E, F} :: S, H} // -> { { v_f :: C, E, F} : S, H} - return todo_.FinishAction(act.results()[0]->GetField( - arena_, FieldPath(access.field()), exp.source_loc())); + std::optional> witness = std::nullopt; + if (access.impl().has_value()) { + auto witness_addr = + todo_.ValueOfNode(*access.impl(), access.source_loc()); + witness = cast( + heap_.Read(llvm::cast(witness_addr)->address(), + access.source_loc())); + } + FieldPath::Component field(access.field(), witness); + Nonnull member = act.results()[0]->GetField( + arena_, FieldPath(field), exp.source_loc()); + return todo_.FinishAction(member); } } case ExpressionKind::IdentifierExpression: { @@ -506,7 +520,7 @@ void Interpreter::StepExp() { const auto& ident = cast(exp); // { {x :: C, E, F} :: S, H} -> { {H(E(x)) :: C, E, F} :: S, H} Nonnull value = - todo_.ValueOfName(ident.named_entity(), ident.source_loc()); + todo_.ValueOfNode(ident.value_node(), ident.source_loc()); if (const auto* lvalue = dyn_cast(value)) { value = heap_.Read(lvalue->address(), exp.source_loc()); } @@ -567,6 +581,17 @@ void Interpreter::StepExp() { Nonnull converted_args = Convert( act.results()[1], &function.param_pattern().static_type()); RuntimeScope function_scope(&heap_); + // Bring the impl witness tables into scope. + for (const auto& [impl_bind, impl_node] : + cast(exp).impls()) { + Nonnull witness = + todo_.ValueOfNode(impl_node, exp.source_loc()); + if (witness->kind() == Value::Kind::LValue) { + const LValue& lval = cast(*witness); + witness = heap_.Read(lval.address(), exp.source_loc()); + } + function_scope.Initialize(impl_bind, witness); + } CHECK(PatternMatch(&function.param_pattern().value(), converted_args, exp.source_loc(), &function_scope)); @@ -649,7 +674,7 @@ void Interpreter::StepExp() { // -> { fn pt -> rt :: {C, E, F} :: S, H} return todo_.FinishAction(arena_->New( std::vector>(), act.results()[0], - act.results()[1])); + act.results()[1], std::vector>())); } } case ExpressionKind::ContinuationTypeLiteral: { @@ -963,6 +988,8 @@ void Interpreter::StepDeclaration() { case DeclarationKind::FunctionDeclaration: case DeclarationKind::ClassDeclaration: case DeclarationKind::ChoiceDeclaration: + case DeclarationKind::InterfaceDeclaration: + case DeclarationKind::ImplDeclaration: // These declarations have no run-time effects. return todo_.FinishAction(); } diff --git a/executable_semantics/interpreter/interpreter.h b/executable_semantics/interpreter/interpreter.h index d059dfb969f2..31d7457ad055 100644 --- a/executable_semantics/interpreter/interpreter.h +++ b/executable_semantics/interpreter/interpreter.h @@ -15,7 +15,6 @@ #include "executable_semantics/ast/expression.h" #include "executable_semantics/ast/pattern.h" #include "executable_semantics/interpreter/action.h" -#include "executable_semantics/interpreter/action_stack.h" #include "executable_semantics/interpreter/heap.h" #include "executable_semantics/interpreter/value.h" #include "llvm/ADT/ArrayRef.h" diff --git a/executable_semantics/interpreter/resolve_control_flow.cpp b/executable_semantics/interpreter/resolve_control_flow.cpp index 1288170f3a08..c7c1e59953c4 100644 --- a/executable_semantics/interpreter/resolve_control_flow.cpp +++ b/executable_semantics/interpreter/resolve_control_flow.cpp @@ -128,7 +128,22 @@ void ResolveControlFlow(Nonnull declaration) { } break; } - default: + case DeclarationKind::InterfaceDeclaration: { + auto& iface_decl = cast(*declaration); + for (Nonnull member : iface_decl.members()) { + ResolveControlFlow(member); + } + break; + } + case DeclarationKind::ImplDeclaration: { + auto& impl_decl = cast(*declaration); + for (Nonnull member : impl_decl.members()) { + ResolveControlFlow(member); + } + break; + } + case DeclarationKind::ChoiceDeclaration: + case DeclarationKind::VariableDeclaration: // do nothing break; } diff --git a/executable_semantics/interpreter/resolve_names.cpp b/executable_semantics/interpreter/resolve_names.cpp index 3afb23505506..5ed859ad6394 100644 --- a/executable_semantics/interpreter/resolve_names.cpp +++ b/executable_semantics/interpreter/resolve_names.cpp @@ -24,6 +24,15 @@ static void AddExposedNames(const Declaration& declaration, static void AddExposedNames(const Declaration& declaration, StaticScope& enclosing_scope) { switch (declaration.kind()) { + case DeclarationKind::InterfaceDeclaration: { + auto& iface_decl = cast(declaration); + enclosing_scope.Add(iface_decl.name(), &iface_decl); + break; + } + case DeclarationKind::ImplDeclaration: { + // Nothing to do here + break; + } case DeclarationKind::FunctionDeclaration: { auto& func = cast(declaration); enclosing_scope.Add(func.name(), &func); @@ -115,7 +124,7 @@ static void ResolveNames(Expression& expression, break; case ExpressionKind::IdentifierExpression: { auto& identifier = cast(expression); - identifier.set_named_entity( + identifier.set_value_node( enclosing_scope.Resolve(identifier.name(), identifier.source_loc())); break; } @@ -244,6 +253,31 @@ static void ResolveNames(Statement& statement, StaticScope& enclosing_scope) { static void ResolveNames(Declaration& declaration, StaticScope& enclosing_scope) { switch (declaration.kind()) { + case DeclarationKind::InterfaceDeclaration: { + auto& iface = cast(declaration); + StaticScope iface_scope; + iface_scope.AddParent(&enclosing_scope); + iface_scope.Add("Self", iface.self()); + for (Nonnull member : iface.members()) { + AddExposedNames(*member, iface_scope); + } + for (Nonnull member : iface.members()) { + ResolveNames(*member, iface_scope); + } + break; + } + case DeclarationKind::ImplDeclaration: { + auto& impl = cast(declaration); + ResolveNames(impl.interface(), enclosing_scope); + ResolveNames(*impl.impl_type(), enclosing_scope); + for (Nonnull member : impl.members()) { + AddExposedNames(*member, enclosing_scope); + } + for (Nonnull member : impl.members()) { + ResolveNames(*member, enclosing_scope); + } + break; + } case DeclarationKind::FunctionDeclaration: { auto& function = cast(declaration); StaticScope function_scope; diff --git a/executable_semantics/interpreter/type_checker.cpp b/executable_semantics/interpreter/type_checker.cpp index 7ad0ed7f09f8..db04f872f709 100644 --- a/executable_semantics/interpreter/type_checker.cpp +++ b/executable_semantics/interpreter/type_checker.cpp @@ -14,6 +14,7 @@ #include "executable_semantics/ast/declaration.h" #include "executable_semantics/common/arena.h" #include "executable_semantics/common/error.h" +#include "executable_semantics/interpreter/impl_scope.h" #include "executable_semantics/interpreter/interpreter.h" #include "executable_semantics/interpreter/value.h" #include "llvm/ADT/StringExtras.h" @@ -25,18 +26,6 @@ using llvm::isa; namespace Carbon { -// Sets the static type of `*object`. Can be called multiple times on -// the same node, so long as the types are the same on each call. -// T must have static_type, has_static_type, and set_static_type methods. -template -static void SetStaticType(Nonnull object, Nonnull type) { - if (object->has_static_type()) { - CHECK(TypeEqual(&object->static_type(), type)); - } else { - object->set_static_type(type); - } -} - static void SetValue(Nonnull pattern, Nonnull value) { // TODO: find some way to CHECK that `value` is identical to pattern->value(), // if it's already set. Unclear if `ValueEqual` is suitable, because it @@ -93,11 +82,14 @@ static auto IsConcreteType(Nonnull value) -> bool { case Value::Kind::PointerType: case Value::Kind::StructType: case Value::Kind::NominalClassType: + case Value::Kind::InterfaceType: + case Value::Kind::Witness: case Value::Kind::ChoiceType: case Value::Kind::ContinuationType: case Value::Kind::VariableType: case Value::Kind::StringType: case Value::Kind::TypeOfClassType: + case Value::Kind::TypeOfInterfaceType: case Value::Kind::TypeOfChoiceType: return true; case Value::Kind::AutoType: @@ -207,10 +199,10 @@ static void ExpectType(SourceLocation source_loc, const std::string& context, } } -void TypeChecker::ArgumentDeduction( - SourceLocation source_loc, - std::map, Nonnull>& deduced, - Nonnull param, Nonnull arg) { +void TypeChecker::ArgumentDeduction(SourceLocation source_loc, + BindingMap& deduced, + Nonnull param, + Nonnull arg) { switch (param->kind()) { case Value::Kind::VariableType: { const auto& var_type = cast(*param); @@ -302,16 +294,19 @@ void TypeChecker::ArgumentDeduction( // For the following cases, we check for type convertability. case Value::Kind::ContinuationType: case Value::Kind::NominalClassType: + case Value::Kind::InterfaceType: case Value::Kind::ChoiceType: case Value::Kind::IntType: case Value::Kind::BoolType: case Value::Kind::TypeType: case Value::Kind::StringType: case Value::Kind::TypeOfClassType: + case Value::Kind::TypeOfInterfaceType: case Value::Kind::TypeOfChoiceType: ExpectType(source_loc, "argument deduction", param, arg); return; // The rest of these cases should never happen. + case Value::Kind::Witness: case Value::Kind::IntValue: case Value::Kind::BoolValue: case Value::Kind::FunctionValue: @@ -361,7 +356,8 @@ auto TypeChecker::Substitute( auto param = Substitute(dict, &fn_type.parameters()); auto ret = Substitute(dict, &fn_type.return_type()); return arena_->New( - std::vector>(), param, ret); + std::vector>(), param, ret, + std::vector>()); } case Value::Kind::PointerType: { return arena_->New( @@ -372,13 +368,16 @@ auto TypeChecker::Substitute( case Value::Kind::BoolType: case Value::Kind::TypeType: case Value::Kind::NominalClassType: + case Value::Kind::InterfaceType: case Value::Kind::ChoiceType: case Value::Kind::ContinuationType: case Value::Kind::StringType: case Value::Kind::TypeOfClassType: + case Value::Kind::TypeOfInterfaceType: case Value::Kind::TypeOfChoiceType: return type; // The rest of these cases should never happen. + case Value::Kind::Witness: case Value::Kind::IntValue: case Value::Kind::BoolValue: case Value::Kind::FunctionValue: @@ -396,16 +395,18 @@ auto TypeChecker::Substitute( } } -void TypeChecker::TypeCheckExp(Nonnull e) { +void TypeChecker::TypeCheckExp(Nonnull e, + const ImplScope& impl_scope) { if (trace_) { - llvm::outs() << "checking expression " << *e << "\nconstants: "; + llvm::outs() << "checking expression " << *e; + llvm::outs() << "\nconstants: "; PrintConstants(llvm::outs()); llvm::outs() << "\n"; } switch (e->kind()) { case ExpressionKind::IndexExpression: { auto& index = cast(*e); - TypeCheckExp(&index.aggregate()); + TypeCheckExp(&index.aggregate(), impl_scope); const Value& aggregate_type = index.aggregate().static_type(); switch (aggregate_type.kind()) { case Value::Kind::TupleValue: { @@ -416,7 +417,7 @@ void TypeChecker::TypeCheckExp(Nonnull e) { FATAL_COMPILATION_ERROR(e->source_loc()) << "index " << i << " is out of range for type " << tuple_type; } - SetStaticType(&index, tuple_type.elements()[i]); + index.set_static_type(tuple_type.elements()[i]); index.set_value_category(index.aggregate().value_category()); return; } @@ -427,27 +428,27 @@ void TypeChecker::TypeCheckExp(Nonnull e) { case ExpressionKind::TupleLiteral: { std::vector> arg_types; for (auto& arg : cast(*e).fields()) { - TypeCheckExp(arg); + TypeCheckExp(arg, impl_scope); arg_types.push_back(&arg->static_type()); } - SetStaticType(e, arena_->New(std::move(arg_types))); + e->set_static_type(arena_->New(std::move(arg_types))); e->set_value_category(ValueCategory::Let); return; } case ExpressionKind::StructLiteral: { std::vector arg_types; for (auto& arg : cast(*e).fields()) { - TypeCheckExp(&arg.expression()); + TypeCheckExp(&arg.expression(), impl_scope); arg_types.push_back({arg.name(), &arg.expression().static_type()}); } - SetStaticType(e, arena_->New(std::move(arg_types))); + e->set_static_type(arena_->New(std::move(arg_types))); e->set_value_category(ValueCategory::Let); return; } case ExpressionKind::StructTypeLiteral: { auto& struct_type = cast(*e); for (auto& arg : struct_type.fields()) { - TypeCheckExp(&arg.expression()); + TypeCheckExp(&arg.expression(), impl_scope); ExpectIsConcreteType(arg.expression().source_loc(), InterpExp(&arg.expression(), arena_, trace_)); } @@ -456,23 +457,23 @@ void TypeChecker::TypeCheckExp(Nonnull e) { // This applies only if there are no fields, because (unlike with // tuples) non-empty struct types are syntactically disjoint // from non-empty struct values. - SetStaticType(&struct_type, arena_->New()); + struct_type.set_static_type(arena_->New()); } else { - SetStaticType(&struct_type, arena_->New()); + struct_type.set_static_type(arena_->New()); } e->set_value_category(ValueCategory::Let); return; } case ExpressionKind::FieldAccessExpression: { auto& access = cast(*e); - TypeCheckExp(&access.aggregate()); + TypeCheckExp(&access.aggregate(), impl_scope); const Value& aggregate_type = access.aggregate().static_type(); switch (aggregate_type.kind()) { case Value::Kind::StructType: { const auto& struct_type = cast(aggregate_type); for (const auto& [field_name, field_type] : struct_type.fields()) { if (access.field() == field_name) { - SetStaticType(&access, field_type); + access.set_static_type(field_type); access.set_value_category(access.aggregate().value_category()); return; } @@ -484,9 +485,9 @@ void TypeChecker::TypeCheckExp(Nonnull e) { case Value::Kind::NominalClassType: { const auto& t_class = cast(aggregate_type); if (std::optional> member = - t_class.FindMember(access.field()); + FindMember(access.field(), t_class.declaration().members()); member.has_value()) { - SetStaticType(&access, &(*member)->static_type()); + access.set_static_type(&(*member)->static_type()); switch ((*member)->kind()) { case DeclarationKind::VariableDeclaration: access.set_value_category(access.aggregate().value_category()); @@ -516,18 +517,17 @@ void TypeChecker::TypeCheckExp(Nonnull e) { << "choice " << choice.name() << " does not have a field named " << access.field(); } - SetStaticType(&access, - arena_->New( - std::vector>(), - *parameter_types, &aggregate_type)); + access.set_static_type(arena_->New( + std::vector>(), *parameter_types, + &aggregate_type, std::vector>())); 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()); + if (std::optional> member = FindMember( + access.field(), class_type.declaration().members()); member.has_value()) { switch ((*member)->kind()) { case DeclarationKind::FunctionDeclaration: { @@ -535,7 +535,7 @@ void TypeChecker::TypeCheckExp(Nonnull e) { if (func->is_method()) { break; } - SetStaticType(&access, &(*member)->static_type()); + access.set_static_type(&(*member)->static_type()); access.set_value_category(ValueCategory::Let); return; } @@ -550,6 +550,39 @@ void TypeChecker::TypeCheckExp(Nonnull e) { << access.field(); } } + case Value::Kind::VariableType: { + const VariableType& var_type = cast(aggregate_type); + const Value& typeof_var = var_type.binding().static_type(); + switch (typeof_var.kind()) { + case Value::Kind::InterfaceType: { + const InterfaceType& iface_type = cast(typeof_var); + const InterfaceDeclaration& iface_decl = iface_type.declaration(); + if (std::optional> member = + FindMember(access.field(), iface_decl.members()); + member.has_value()) { + const Value& member_type = (*member)->static_type(); + std::map, Nonnull> + self_map; + self_map[iface_decl.self()] = &var_type; + Nonnull inst_member_type = + Substitute(self_map, &member_type); + access.set_static_type(inst_member_type); + access.set_impl(*var_type.binding().impl_binding()); + return; + } else { + FATAL_COMPILATION_ERROR(e->source_loc()) + << "field access, " << access.field() << " not in " + << iface_decl.name(); + } + break; + } + default: + break; + } + FATAL_COMPILATION_ERROR(e->source_loc()) + << "field access, unexpected " << aggregate_type << " in " << *e; + break; + } default: FATAL_COMPILATION_ERROR(e->source_loc()) << "field access, unexpected " << aggregate_type << " in " << *e; @@ -557,40 +590,40 @@ void TypeChecker::TypeCheckExp(Nonnull e) { } case ExpressionKind::IdentifierExpression: { auto& ident = cast(*e); - if (ident.named_entity().base().kind() == + if (ident.value_node().base().kind() == AstNodeKind::FunctionDeclaration) { const auto& function = - cast(ident.named_entity().base()); + cast(ident.value_node().base()); if (!function.has_static_type()) { CHECK(function.return_term().is_auto()); FATAL_COMPILATION_ERROR(ident.source_loc()) << "Function calls itself, but has a deduced return type"; } } - SetStaticType(&ident, &ident.named_entity().static_type()); - ident.set_value_category(ident.named_entity().value_category()); + ident.set_static_type(&ident.value_node().static_type()); + ident.set_value_category(ident.value_node().value_category()); return; } case ExpressionKind::IntLiteral: e->set_value_category(ValueCategory::Let); - SetStaticType(e, arena_->New()); + e->set_static_type(arena_->New()); return; case ExpressionKind::BoolLiteral: e->set_value_category(ValueCategory::Let); - SetStaticType(e, arena_->New()); + e->set_static_type(arena_->New()); return; case ExpressionKind::PrimitiveOperatorExpression: { auto& op = cast(*e); std::vector> ts; for (Nonnull argument : op.arguments()) { - TypeCheckExp(argument); + TypeCheckExp(argument, impl_scope); ts.push_back(&argument->static_type()); } switch (op.op()) { case Operator::Neg: ExpectExactType(e->source_loc(), "negation", arena_->New(), ts[0]); - SetStaticType(&op, arena_->New()); + op.set_static_type(arena_->New()); op.set_value_category(ValueCategory::Let); return; case Operator::Add: @@ -598,7 +631,7 @@ void TypeChecker::TypeCheckExp(Nonnull e) { arena_->New(), ts[0]); ExpectExactType(e->source_loc(), "addition(2)", arena_->New(), ts[1]); - SetStaticType(&op, arena_->New()); + op.set_static_type(arena_->New()); op.set_value_category(ValueCategory::Let); return; case Operator::Sub: @@ -606,7 +639,7 @@ void TypeChecker::TypeCheckExp(Nonnull e) { arena_->New(), ts[0]); ExpectExactType(e->source_loc(), "subtraction(2)", arena_->New(), ts[1]); - SetStaticType(&op, arena_->New()); + op.set_static_type(arena_->New()); op.set_value_category(ValueCategory::Let); return; case Operator::Mul: @@ -614,7 +647,7 @@ void TypeChecker::TypeCheckExp(Nonnull e) { arena_->New(), ts[0]); ExpectExactType(e->source_loc(), "multiplication(2)", arena_->New(), ts[1]); - SetStaticType(&op, arena_->New()); + op.set_static_type(arena_->New()); op.set_value_category(ValueCategory::Let); return; case Operator::And: @@ -622,7 +655,7 @@ void TypeChecker::TypeCheckExp(Nonnull e) { ts[0]); ExpectExactType(e->source_loc(), "&&(2)", arena_->New(), ts[1]); - SetStaticType(&op, arena_->New()); + op.set_static_type(arena_->New()); op.set_value_category(ValueCategory::Let); return; case Operator::Or: @@ -630,27 +663,27 @@ void TypeChecker::TypeCheckExp(Nonnull e) { ts[0]); ExpectExactType(e->source_loc(), "||(2)", arena_->New(), ts[1]); - SetStaticType(&op, arena_->New()); + op.set_static_type(arena_->New()); op.set_value_category(ValueCategory::Let); return; case Operator::Not: ExpectExactType(e->source_loc(), "!", arena_->New(), ts[0]); - SetStaticType(&op, arena_->New()); + op.set_static_type(arena_->New()); op.set_value_category(ValueCategory::Let); return; case Operator::Eq: ExpectExactType(e->source_loc(), "==", ts[0], ts[1]); - SetStaticType(&op, arena_->New()); + op.set_static_type(arena_->New()); op.set_value_category(ValueCategory::Let); return; case Operator::Deref: ExpectPointerType(e->source_loc(), "*", ts[0]); - SetStaticType(&op, &cast(*ts[0]).type()); + op.set_static_type(&cast(*ts[0]).type()); op.set_value_category(ValueCategory::Var); return; case Operator::Ptr: ExpectExactType(e->source_loc(), "*", arena_->New(), ts[0]); - SetStaticType(&op, arena_->New()); + op.set_static_type(arena_->New()); op.set_value_category(ValueCategory::Let); return; case Operator::AddressOf: @@ -659,7 +692,7 @@ void TypeChecker::TypeCheckExp(Nonnull e) { << "Argument to " << ToString(op.op()) << " should be an lvalue."; } - SetStaticType(&op, arena_->New(ts[0])); + op.set_static_type(arena_->New(ts[0])); op.set_value_category(ValueCategory::Let); return; } @@ -667,16 +700,15 @@ void TypeChecker::TypeCheckExp(Nonnull e) { } case ExpressionKind::CallExpression: { auto& call = cast(*e); - TypeCheckExp(&call.function()); + TypeCheckExp(&call.function(), impl_scope); switch (call.function().static_type().kind()) { case Value::Kind::FunctionType: { const auto& fun_t = cast(call.function().static_type()); - TypeCheckExp(&call.argument()); + TypeCheckExp(&call.argument(), impl_scope); Nonnull parameters = &fun_t.parameters(); Nonnull return_type = &fun_t.return_type(); if (!fun_t.deduced().empty()) { - std::map, Nonnull> - deduced_args; + BindingMap deduced_args; ArgumentDeduction(e->source_loc(), deduced_args, parameters, &call.argument().static_type()); for (Nonnull deduced_param : @@ -692,11 +724,32 @@ void TypeChecker::TypeCheckExp(Nonnull e) { } parameters = Substitute(deduced_args, parameters); return_type = Substitute(deduced_args, return_type); + // Find impls for all the impl bindings of the function + std::map, ValueNodeView> impls; + for (Nonnull impl_binding : + fun_t.impl_bindings()) { + switch (impl_binding->interface()->kind()) { + case Value::Kind::InterfaceType: { + ValueNodeView impl = impl_scope.Resolve( + impl_binding->interface(), + deduced_args[impl_binding->type_var()], e->source_loc()); + impls.emplace(impl_binding, impl); + break; + } + case Value::Kind::TypeType: + break; + default: + FATAL_COMPILATION_ERROR(e->source_loc()) + << "unexpected type of deduced parameter " + << *impl_binding->interface(); + } + } + call.set_impls(impls); } else { ExpectType(e->source_loc(), "call", parameters, &call.argument().static_type()); } - SetStaticType(&call, return_type); + call.set_static_type(return_type); call.set_value_category(ValueCategory::Let); return; } @@ -714,17 +767,17 @@ void TypeChecker::TypeCheckExp(Nonnull e) { InterpExp(&fn.parameter(), arena_, trace_)); ExpectIsConcreteType(fn.return_type().source_loc(), InterpExp(&fn.return_type(), arena_, trace_)); - SetStaticType(&fn, arena_->New()); + fn.set_static_type(arena_->New()); fn.set_value_category(ValueCategory::Let); return; } case ExpressionKind::StringLiteral: - SetStaticType(e, arena_->New()); + e->set_static_type(arena_->New()); e->set_value_category(ValueCategory::Let); return; case ExpressionKind::IntrinsicExpression: { auto& intrinsic_exp = cast(*e); - TypeCheckExp(&intrinsic_exp.args()); + TypeCheckExp(&intrinsic_exp.args(), impl_scope); switch (cast(*e).intrinsic()) { case IntrinsicExpression::Intrinsic::Print: if (intrinsic_exp.args().fields().size() != 1) { @@ -734,7 +787,7 @@ void TypeChecker::TypeCheckExp(Nonnull e) { ExpectType(e->source_loc(), "__intrinsic_print argument", arena_->New(), &intrinsic_exp.args().fields()[0]->static_type()); - SetStaticType(e, TupleValue::Empty()); + e->set_static_type(TupleValue::Empty()); e->set_value_category(ValueCategory::Let); return; } @@ -745,7 +798,7 @@ void TypeChecker::TypeCheckExp(Nonnull e) { case ExpressionKind::TypeTypeLiteral: case ExpressionKind::ContinuationTypeLiteral: e->set_value_category(ValueCategory::Let); - SetStaticType(e, arena_->New()); + e->set_static_type(arena_->New()); return; case ExpressionKind::UnimplementedExpression: FATAL() << "Unimplemented: " << *e; @@ -753,7 +806,8 @@ void TypeChecker::TypeCheckExp(Nonnull e) { } void TypeChecker::TypeCheckPattern( - Nonnull p, std::optional> expected) { + Nonnull p, std::optional> expected, + const ImplScope& impl_scope) { if (trace_) { llvm::outs() << "checking pattern " << *p; if (expected) { @@ -765,12 +819,12 @@ void TypeChecker::TypeCheckPattern( } switch (p->kind()) { case PatternKind::AutoPattern: { - SetStaticType(p, arena_->New()); + p->set_static_type(arena_->New()); return; } case PatternKind::BindingPattern: { auto& binding = cast(*p); - TypeCheckPattern(&binding.type(), std::nullopt); + TypeCheckPattern(&binding.type(), std::nullopt, impl_scope); Nonnull type = InterpPattern(&binding.type(), arena_, trace_); if (expected) { @@ -787,7 +841,7 @@ void TypeChecker::TypeCheckPattern( } } ExpectIsConcreteType(binding.source_loc(), type); - SetStaticType(&binding, type); + binding.set_static_type(type); SetValue(&binding, InterpPattern(&binding, arena_, trace_)); return; } @@ -808,16 +862,16 @@ void TypeChecker::TypeCheckPattern( if (expected) { expected_field_type = cast(**expected).elements()[i]; } - TypeCheckPattern(field, expected_field_type); + TypeCheckPattern(field, expected_field_type, impl_scope); field_types.push_back(&field->static_type()); } - SetStaticType(&tuple, arena_->New(std::move(field_types))); + tuple.set_static_type(arena_->New(std::move(field_types))); SetValue(&tuple, InterpPattern(&tuple, arena_, trace_)); return; } case PatternKind::AlternativePattern: { auto& alternative = cast(*p); - TypeCheckExp(&alternative.choice_type()); + TypeCheckExp(&alternative.choice_type(), impl_scope); if (alternative.choice_type().static_type().kind() != Value::Kind::TypeOfChoiceType) { FATAL_COMPILATION_ERROR(alternative.source_loc()) @@ -838,40 +892,45 @@ void TypeChecker::TypeCheckPattern( << "'" << alternative.alternative_name() << "' is not an alternative of " << choice_type; } - TypeCheckPattern(&alternative.arguments(), *parameter_types); - SetStaticType(&alternative, &choice_type); + TypeCheckPattern(&alternative.arguments(), *parameter_types, impl_scope); + alternative.set_static_type(&choice_type); SetValue(&alternative, InterpPattern(&alternative, arena_, trace_)); return; } case PatternKind::ExpressionPattern: { auto& expression = cast(*p).expression(); - TypeCheckExp(&expression); - SetStaticType(p, &expression.static_type()); + TypeCheckExp(&expression, impl_scope); + p->set_static_type(&expression.static_type()); SetValue(p, InterpPattern(p, arena_, trace_)); return; } } } -void TypeChecker::TypeCheckStmt(Nonnull s) { +void TypeChecker::TypeCheckStmt(Nonnull s, + const ImplScope& impl_scope) { + if (trace_) { + llvm::outs() << "checking statement " << *s << "\n"; + } switch (s->kind()) { case StatementKind::Match: { auto& match = cast(*s); - TypeCheckExp(&match.expression()); + TypeCheckExp(&match.expression(), impl_scope); std::vector new_clauses; for (auto& clause : match.clauses()) { - TypeCheckPattern(&clause.pattern(), &match.expression().static_type()); - TypeCheckStmt(&clause.statement()); + TypeCheckPattern(&clause.pattern(), &match.expression().static_type(), + impl_scope); + TypeCheckStmt(&clause.statement(), impl_scope); } return; } case StatementKind::While: { auto& while_stmt = cast(*s); - TypeCheckExp(&while_stmt.condition()); + TypeCheckExp(&while_stmt.condition(), impl_scope); ExpectType(s->source_loc(), "condition of `while`", arena_->New(), &while_stmt.condition().static_type()); - TypeCheckStmt(&while_stmt.body()); + TypeCheckStmt(&while_stmt.body(), impl_scope); return; } case StatementKind::Break: @@ -880,21 +939,21 @@ void TypeChecker::TypeCheckStmt(Nonnull s) { case StatementKind::Block: { auto& block = cast(*s); for (auto* block_statement : block.statements()) { - TypeCheckStmt(block_statement); + TypeCheckStmt(block_statement, impl_scope); } return; } case StatementKind::VariableDefinition: { auto& var = cast(*s); - TypeCheckExp(&var.init()); + TypeCheckExp(&var.init(), impl_scope); const Value& rhs_ty = var.init().static_type(); - TypeCheckPattern(&var.pattern(), &rhs_ty); + TypeCheckPattern(&var.pattern(), &rhs_ty, impl_scope); return; } case StatementKind::Assign: { auto& assign = cast(*s); - TypeCheckExp(&assign.rhs()); - TypeCheckExp(&assign.lhs()); + TypeCheckExp(&assign.rhs(), impl_scope); + TypeCheckExp(&assign.lhs(), impl_scope); ExpectType(s->source_loc(), "assign", &assign.lhs().static_type(), &assign.rhs().static_type()); if (assign.lhs().value_category() != ValueCategory::Var) { @@ -904,26 +963,26 @@ void TypeChecker::TypeCheckStmt(Nonnull s) { return; } case StatementKind::ExpressionStatement: { - TypeCheckExp(&cast(*s).expression()); + TypeCheckExp(&cast(*s).expression(), impl_scope); return; } case StatementKind::If: { auto& if_stmt = cast(*s); - TypeCheckExp(&if_stmt.condition()); + TypeCheckExp(&if_stmt.condition(), impl_scope); ExpectType(s->source_loc(), "condition of `if`", arena_->New(), &if_stmt.condition().static_type()); - TypeCheckStmt(&if_stmt.then_block()); + TypeCheckStmt(&if_stmt.then_block(), impl_scope); if (if_stmt.else_block()) { - TypeCheckStmt(*if_stmt.else_block()); + TypeCheckStmt(*if_stmt.else_block(), impl_scope); } return; } case StatementKind::Return: { auto& ret = cast(*s); - TypeCheckExp(&ret.expression()); + TypeCheckExp(&ret.expression(), impl_scope); ReturnTerm& return_term = ret.function().return_term(); if (return_term.is_auto()) { - SetStaticType(&return_term, &ret.expression().static_type()); + return_term.set_static_type(&ret.expression().static_type()); } else { ExpectType(s->source_loc(), "return", &return_term.static_type(), &ret.expression().static_type()); @@ -932,13 +991,13 @@ void TypeChecker::TypeCheckStmt(Nonnull s) { } case StatementKind::Continuation: { auto& cont = cast(*s); - TypeCheckStmt(&cont.body()); - SetStaticType(&cont, arena_->New()); + TypeCheckStmt(&cont.body(), impl_scope); + cont.set_static_type(arena_->New()); return; } case StatementKind::Run: { auto& run = cast(*s); - TypeCheckExp(&run.argument()); + TypeCheckExp(&run.argument(), impl_scope); ExpectType(s->source_loc(), "argument of `run`", arena_->New(), &run.argument().static_type()); @@ -1025,22 +1084,33 @@ void TypeChecker::ExpectReturnOnAllPaths( // TODO: Add checking to function definitions to ensure that // all deduced type parameters will be deduced. -void TypeChecker::TypeCheckFunctionDeclaration(Nonnull f, - bool check_body) { +void TypeChecker::DeclareFunctionDeclaration(Nonnull f, + const ImplScope& impl_scope) { + if (trace_) { + llvm::outs() << "** declaring function " << f->name() << "\n"; + } // Bring the deduced parameters into scope for (Nonnull deduced : f->deduced_parameters()) { - TypeCheckExp(&deduced->type()); - // auto t = interpreter_.InterpExp(values, deduced.type); - SetStaticType(deduced, arena_->New(deduced)); - SetConstantValue(deduced, &deduced->static_type()); + TypeCheckExp(&deduced->type(), impl_scope); + SetConstantValue(deduced, arena_->New(deduced)); + deduced->set_static_type(InterpExp(&deduced->type(), arena_, trace_)); } + // Type check the receiver pattern if (f->is_method()) { - // Type check the receiver patter - TypeCheckPattern(&f->me_pattern(), std::nullopt); + TypeCheckPattern(&f->me_pattern(), std::nullopt, impl_scope); } - // Type check the parameter pattern - TypeCheckPattern(&f->param_pattern(), std::nullopt); + TypeCheckPattern(&f->param_pattern(), std::nullopt, impl_scope); + + // Create the impl_bindings + std::vector> impl_bindings; + for (Nonnull deduced : f->deduced_parameters()) { + Nonnull impl_binding = arena_->New( + deduced->source_loc(), deduced, &deduced->static_type()); + deduced->set_impl_binding(impl_binding); + impl_binding->set_static_type(&deduced->static_type()); + impl_bindings.push_back(impl_binding); + } // Evaluate the return type, if we can do so without examining the body. if (std::optional> return_expression = @@ -1048,31 +1118,39 @@ void TypeChecker::TypeCheckFunctionDeclaration(Nonnull f, return_expression.has_value()) { // We ignore the return value because return type expressions can't bring // new types into scope. - TypeCheckExp(*return_expression); - SetStaticType(&f->return_term(), - InterpExp(*return_expression, arena_, trace_)); + TypeCheckExp(*return_expression, impl_scope); + // Should we be doing SetConstantValue instead? -Jeremy + // And shouldn't the type of this be Type? + f->return_term().set_static_type( + InterpExp(*return_expression, arena_, trace_)); } else if (f->return_term().is_omitted()) { - SetStaticType(&f->return_term(), TupleValue::Empty()); + f->return_term().set_static_type(TupleValue::Empty()); } else { // We have to type-check the body in order to determine the return type. - check_body = true; if (!f->body().has_value()) { FATAL_COMPILATION_ERROR(f->return_term().source_loc()) << "Function declaration has deduced return type but no body"; } - } - - if (f->body().has_value() && check_body) { - TypeCheckStmt(*f->body()); + // Bring the impl bindings into scope + ImplScope function_scope; + function_scope.AddParent(&impl_scope); + for (Nonnull impl_binding : impl_bindings) { + function_scope.Add(impl_binding->interface(), + *impl_binding->type_var()->constant_value(), + impl_binding); + } + TypeCheckStmt(*f->body(), impl_scope); if (!f->return_term().is_omitted()) { ExpectReturnOnAllPaths(f->body(), f->source_loc()); } } ExpectIsConcreteType(f->source_loc(), &f->return_term().static_type()); - SetStaticType(f, arena_->New(f->deduced_parameters(), - &f->param_pattern().static_type(), - &f->return_term().static_type())); + f->set_static_type(arena_->New( + f->deduced_parameters(), &f->param_pattern().static_type(), + &f->return_term().static_type(), impl_bindings)); + SetConstantValue(f, arena_->New(f)); + if (f->name() == "Main") { if (!f->return_term().type_expression().has_value()) { FATAL_COMPILATION_ERROR(f->return_term().source_loc()) @@ -1082,68 +1160,191 @@ 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)); + + if (trace_) { + llvm::outs() << "** finished declaring function " << f->name() << "\n"; + } return; } -void TypeChecker::TypeCheckClassDeclaration( - Nonnull class_decl) { +void TypeChecker::TypeCheckFunctionDeclaration(Nonnull f, + const ImplScope& impl_scope) { + if (trace_) { + llvm::outs() << "** checking function " << f->name() << "\n"; + } + // if f->return_term().is_auto(), the function body was already + // type checked in DeclareFunctionDeclaration + if (f->body().has_value() && !f->return_term().is_auto()) { + // Bring the impl's into scope + ImplScope function_scope; + function_scope.AddParent(&impl_scope); + for (Nonnull impl_binding : + cast(f->static_type()).impl_bindings()) { + function_scope.Add(impl_binding->interface(), + *impl_binding->type_var()->constant_value(), + impl_binding); + } + TypeCheckStmt(*f->body(), function_scope); + if (!f->return_term().is_omitted()) { + ExpectReturnOnAllPaths(f->body(), f->source_loc()); + } + } + if (trace_) { + llvm::outs() << "** finished checking function " << f->name() << "\n"; + } + return; +} + +void TypeChecker::DeclareClassDeclaration(Nonnull class_decl, + ImplScope& enclosing_scope) { // 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)); + class_decl->set_static_type(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); + DeclareDeclaration(m, enclosing_scope); } } -void TypeChecker::TypeCheckChoiceDeclaration( - Nonnull choice) { +void TypeChecker::TypeCheckClassDeclaration( + Nonnull class_decl, const ImplScope& impl_scope) { + for (Nonnull m : class_decl->members()) { + TypeCheckDeclaration(m, impl_scope); + } +} + +void TypeChecker::DeclareInterfaceDeclaration( + Nonnull iface_decl, ImplScope& enclosing_scope) { + Nonnull iface_type = arena_->New(iface_decl); + SetConstantValue(iface_decl, iface_type); + iface_decl->set_static_type(arena_->New(iface_type)); + + // Process the Self parameter. + TypeCheckExp(&iface_decl->self()->type(), enclosing_scope); + iface_decl->self()->set_static_type( + arena_->New(iface_decl->self())); + SetConstantValue(iface_decl->self(), &iface_decl->self()->static_type()); + + for (Nonnull m : iface_decl->members()) { + DeclareDeclaration(m, enclosing_scope); + } +} + +void TypeChecker::TypeCheckInterfaceDeclaration( + Nonnull iface_decl, const ImplScope& impl_scope) { + for (Nonnull m : iface_decl->members()) { + TypeCheckDeclaration(m, impl_scope); + } +} + +void TypeChecker::DeclareImplDeclaration(Nonnull impl_decl, + ImplScope& enclosing_scope) { + if (trace_) { + llvm::outs() << "declaring " << *impl_decl << "\n"; + } + TypeCheckExp(&impl_decl->interface(), enclosing_scope); + Nonnull iface_type = + InterpExp(&impl_decl->interface(), arena_, trace_); + const auto& iface_decl = cast(*iface_type).declaration(); + impl_decl->set_interface_type(iface_type); + + TypeCheckExp(impl_decl->impl_type(), enclosing_scope); + Nonnull impl_type_value = + InterpExp(impl_decl->impl_type(), arena_, trace_); + enclosing_scope.Add(iface_type, impl_type_value, impl_decl); + + for (Nonnull m : impl_decl->members()) { + DeclareDeclaration(m, enclosing_scope); + } + // Check that the interface is satisfied by the impl members + for (Nonnull m : iface_decl.members()) { + if (std::optional mem_name = GetName(*m); + mem_name.has_value()) { + if (std::optional> mem = + FindMember(*mem_name, impl_decl->members()); + mem.has_value()) { + std::map, Nonnull> + self_map; + self_map[iface_decl.self()] = impl_type_value; + Nonnull iface_mem_type = + Substitute(self_map, &m->static_type()); + ExpectType((*mem)->source_loc(), "member of implementation", + iface_mem_type, &(*mem)->static_type()); + } else { + FATAL_COMPILATION_ERROR(impl_decl->source_loc()) + << "implementation missing " << *mem_name; + } + } + } + impl_decl->set_constant_value(arena_->New(impl_decl)); +} + +void TypeChecker::TypeCheckImplDeclaration(Nonnull impl_decl, + const ImplScope& impl_scope) { + if (trace_) { + llvm::outs() << "checking " << *impl_decl << "\n"; + } + for (Nonnull m : impl_decl->members()) { + TypeCheckDeclaration(m, impl_scope); + } + if (trace_) { + llvm::outs() << "finished checking impl\n"; + } +} + +void TypeChecker::DeclareChoiceDeclaration(Nonnull choice, + const ImplScope& impl_scope) { std::vector alternatives; for (Nonnull alternative : choice->alternatives()) { - TypeCheckExp(&alternative->signature()); + TypeCheckExp(&alternative->signature(), impl_scope); auto signature = InterpExp(&alternative->signature(), arena_, trace_); 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)); + choice->set_static_type(arena_->New(ct)); +} + +void TypeChecker::TypeCheckChoiceDeclaration(Nonnull choice, + const ImplScope& impl_scope) { + // Nothing to do here, but perhaps that will change in the future? } void TypeChecker::TypeCheck(AST& ast) { + ImplScope impl_scope; for (Nonnull declaration : ast.declarations) { - DeclareDeclaration(declaration); + DeclareDeclaration(declaration, impl_scope); } for (Nonnull decl : ast.declarations) { - TypeCheckDeclaration(decl); + TypeCheckDeclaration(decl, impl_scope); } - TypeCheckExp(*ast.main_call); + TypeCheckExp(*ast.main_call, impl_scope); } -void TypeChecker::TypeCheckDeclaration(Nonnull d) { +void TypeChecker::TypeCheckDeclaration(Nonnull d, + const ImplScope& impl_scope) { switch (d->kind()) { + case DeclarationKind::InterfaceDeclaration: { + TypeCheckInterfaceDeclaration(&cast(*d), + impl_scope); + break; + } + case DeclarationKind::ImplDeclaration: { + TypeCheckImplDeclaration(&cast(*d), impl_scope); + break; + } case DeclarationKind::FunctionDeclaration: - TypeCheckFunctionDeclaration(&cast(*d), - /*check_body=*/true); + TypeCheckFunctionDeclaration(&cast(*d), impl_scope); return; case DeclarationKind::ClassDeclaration: - TypeCheckClassDeclaration(&cast(*d)); + TypeCheckClassDeclaration(&cast(*d), impl_scope); return; case DeclarationKind::ChoiceDeclaration: - TypeCheckChoiceDeclaration(&cast(*d)); + TypeCheckChoiceDeclaration(&cast(*d), impl_scope); return; case DeclarationKind::VariableDeclaration: { auto& var = cast(*d); @@ -1151,7 +1352,7 @@ void TypeChecker::TypeCheckDeclaration(Nonnull d) { // the declared type of the variable, otherwise returns this // declaration with annotated types. if (var.has_initializer()) { - TypeCheckExp(&var.initializer()); + TypeCheckExp(&var.initializer(), impl_scope); } const auto* binding_type = dyn_cast(&var.binding().type()); @@ -1160,35 +1361,43 @@ void TypeChecker::TypeCheckDeclaration(Nonnull d) { FATAL_COMPILATION_ERROR(var.source_loc()) << "Type of a top-level variable must be an expression."; } - Nonnull declared_type = - InterpExp(&binding_type->expression(), arena_, trace_); - SetStaticType(&var, declared_type); if (var.has_initializer()) { - ExpectType(var.source_loc(), "initializer of variable", declared_type, - &var.initializer().static_type()); + ExpectType(var.source_loc(), "initializer of variable", + &var.static_type(), &var.initializer().static_type()); } return; } } } -void TypeChecker::DeclareDeclaration(Nonnull d) { +void TypeChecker::DeclareDeclaration(Nonnull d, + ImplScope& impl_scope) { switch (d->kind()) { + case DeclarationKind::InterfaceDeclaration: { + auto& iface_decl = cast(*d); + DeclareInterfaceDeclaration(&iface_decl, impl_scope); + break; + } + case DeclarationKind::ImplDeclaration: { + auto& impl_decl = cast(*d); + DeclareImplDeclaration(&impl_decl, impl_scope); + break; + } case DeclarationKind::FunctionDeclaration: { auto& func_def = cast(*d); - TypeCheckFunctionDeclaration(&func_def, /*check_body=*/false); + DeclareFunctionDeclaration(&func_def, impl_scope); break; } case DeclarationKind::ClassDeclaration: { auto& class_decl = cast(*d); - TypeCheckClassDeclaration(&class_decl); + DeclareClassDeclaration(&class_decl, impl_scope); break; } case DeclarationKind::ChoiceDeclaration: { auto& choice = cast(*d); - TypeCheckChoiceDeclaration(&choice); + DeclareChoiceDeclaration(&choice, impl_scope); break; } @@ -1198,32 +1407,27 @@ void TypeChecker::DeclareDeclaration(Nonnull d) { // compile-time symbol table. Expression& type = cast(var.binding().type()).expression(); - TypeCheckPattern(&var.binding(), std::nullopt); + TypeCheckPattern(&var.binding(), std::nullopt, impl_scope); Nonnull declared_type = InterpExp(&type, arena_, trace_); - SetStaticType(&var, declared_type); + var.set_static_type(declared_type); break; } } } template -void TypeChecker::SetConstantValue(Nonnull named_entity, +void TypeChecker::SetConstantValue(Nonnull value_node, Nonnull value) { - std::optional> old_value = - named_entity->constant_value(); - if (old_value.has_value()) { - CHECK(ValueEqual(*old_value, value)); - } else { - named_entity->set_constant_value(value); - CHECK(constants_.insert(named_entity).second); - } + std::optional> old_value = value_node->constant_value(); + CHECK(!old_value.has_value()); + value_node->set_constant_value(value); + CHECK(constants_.insert(value_node).second); } void TypeChecker::PrintConstants(llvm::raw_ostream& out) { llvm::ListSeparator sep; - for (const auto& named_entity : constants_) { - out << sep << named_entity.name() << ": " - << **named_entity.constant_value(); + for (const auto& value_node : constants_) { + out << sep << value_node; } } diff --git a/executable_semantics/interpreter/type_checker.h b/executable_semantics/interpreter/type_checker.h index afe2dcb1b7f7..bae4205e53f5 100644 --- a/executable_semantics/interpreter/type_checker.h +++ b/executable_semantics/interpreter/type_checker.h @@ -13,6 +13,7 @@ #include "executable_semantics/ast/statement.h" #include "executable_semantics/common/nonnull.h" #include "executable_semantics/interpreter/dictionary.h" +#include "executable_semantics/interpreter/impl_scope.h" #include "executable_semantics/interpreter/interpreter.h" namespace Carbon { @@ -31,17 +32,16 @@ class TypeChecker { // inside the argument type. // The `deduced` parameter is an accumulator, that is, it holds the // results so-far. - static void ArgumentDeduction( - SourceLocation source_loc, - std::map, Nonnull>& deduced, - Nonnull param, Nonnull arg); + static void ArgumentDeduction(SourceLocation source_loc, BindingMap& deduced, + Nonnull param, + Nonnull arg); // Traverses the AST rooted at `e`, populating the static_type() of all nodes // and ensuring they follow Carbon's typing rules. // // `values` maps variable names to their compile-time values. It is not // directly used in this function but is passed to InterExp. - void TypeCheckExp(Nonnull e); + void TypeCheckExp(Nonnull e, const ImplScope& impl_scope); // Equivalent to TypeCheckExp, but operates on the AST rooted at `p`. // @@ -49,31 +49,64 @@ class TypeChecker { // surrounding context gives us that information. Otherwise, it is // nullopt. void TypeCheckPattern(Nonnull p, - std::optional> expected); - - // Equivalent to TypeCheckExp, but operates on the AST rooted at `d`. - void TypeCheckDeclaration(Nonnull d); + std::optional> expected, + const ImplScope& impl_scope); // Equivalent to TypeCheckExp, but operates on the AST rooted at `s`. // // REQUIRES: f.return_term().has_static_type() || f.return_term().is_auto(), // where `f` is nearest enclosing FunctionDeclaration of `s`. - void TypeCheckStmt(Nonnull s); + void TypeCheckStmt(Nonnull s, const ImplScope& impl_scope); - // Equivalent to TypeCheckExp, but operates on the AST rooted at `f`, - // and may not traverse f->body() if `check_body` is false. + // Establish the `static_type` and `constant_value` of the + // declaration and all of its nested declarations. This involves the + // compile-time interpretation of any type expressions in the + // declaration. It does not involve type checking statements and + // (runtime) expressions, as in the body of a function or a method. + // Dispatches to one of the following functions. + void DeclareDeclaration(Nonnull d, ImplScope& enclosing_scope); + + void DeclareFunctionDeclaration(Nonnull f, + const ImplScope& enclosing_scope); + + void DeclareClassDeclaration(Nonnull class_decl, + ImplScope& enclosing_scope); + + void DeclareInterfaceDeclaration(Nonnull iface_decl, + ImplScope& enclosing_scope); + + void DeclareImplDeclaration(Nonnull impl_decl, + ImplScope& enclosing_scope); + + void DeclareChoiceDeclaration(Nonnull choice, + const ImplScope& enclosing_scope); + + // Checks the statements and (runtime) expressions within the + // declaration, such as the body of a function. + // Dispatches to one of the following functions. + // Assumes that DeclareDeclaration has already been invoked on `d`. + void TypeCheckDeclaration(Nonnull d, + const ImplScope& impl_scope); + + // Type check the body of the function. void TypeCheckFunctionDeclaration(Nonnull f, - bool check_body); + const ImplScope& impl_scope); - // Equivalent to TypeCheckExp, but operates on the AST rooted at class_decl. - void TypeCheckClassDeclaration(Nonnull class_decl); + // Type check all the members of the class. + void TypeCheckClassDeclaration(Nonnull class_decl, + const ImplScope& impl_scope); - // Equivalent to TypeCheckExp, but operates on the AST rooted at choice_decl. - void TypeCheckChoiceDeclaration(Nonnull choice); + // Type check all the members of the interface. + void TypeCheckInterfaceDeclaration(Nonnull iface_decl, + const ImplScope& impl_scope); - // Establish the type of the declaration without deeply checking - // the declaration, such as checking the body of a function. - void DeclareDeclaration(Nonnull d); + // Type check all the members of the implementation. + void TypeCheckImplDeclaration(Nonnull impl_decl, + const ImplScope& impl_scope); + + // This currently does nothing, but perhaps that will change in the future. + void TypeCheckChoiceDeclaration(Nonnull choice, + const ImplScope& impl_scope); // Verifies that opt_stmt holds a statement, and it is structurally impossible // for control flow to leave that statement except via a `return`. @@ -98,7 +131,7 @@ class TypeChecker { void PrintConstants(llvm::raw_ostream& out); Nonnull arena_; - std::set constants_; + std::set constants_; bool trace_; }; diff --git a/executable_semantics/interpreter/value.cpp b/executable_semantics/interpreter/value.cpp index d58446874702..e153c1ae27af 100644 --- a/executable_semantics/interpreter/value.cpp +++ b/executable_semantics/interpreter/value.cpp @@ -28,8 +28,28 @@ auto StructValue::FindField(const std::string& name) const } static auto GetMember(Nonnull arena, Nonnull v, - const std::string& f, SourceLocation source_loc) - -> Nonnull { + const FieldPath::Component& field, + SourceLocation source_loc) -> Nonnull { + const std::string& f = field.name(); + + if (field.witness().has_value()) { + Nonnull witness = *field.witness(); + switch (witness->kind()) { + case Value::Kind::Witness: { + if (std::optional> mem_decl = + FindMember(f, witness->declaration().members()); + mem_decl.has_value()) { + const auto& fun_decl = cast(**mem_decl); + return arena->New(&fun_decl, v); + } else { + FATAL_COMPILATION_ERROR(source_loc) + << "member " << f << " not in " << *witness; + } + } + default: + FATAL() << "expected Witness, not " << *witness; + } + } switch (v->kind()) { case Value::Kind::StructValue: { std::optional> field = @@ -51,8 +71,8 @@ static auto GetMember(Nonnull arena, Nonnull v, 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; + FATAL_RUNTIME_ERROR(source_loc) + << "member " << f << " not in " << *v << " or its " << class_type; } else if ((*func)->declaration().is_method()) { // Found a method. Turn it into a bound method. const FunctionValue& m = cast(**func); @@ -90,17 +110,18 @@ static auto GetMember(Nonnull arena, Nonnull v, auto Value::GetField(Nonnull arena, const FieldPath& path, SourceLocation source_loc) const -> Nonnull { Nonnull value(this); - for (const std::string& field : path.components_) { + for (const FieldPath::Component& field : path.components_) { value = GetMember(arena, value, field, source_loc); } return value; } -static auto SetFieldImpl(Nonnull arena, Nonnull value, - std::vector::const_iterator path_begin, - std::vector::const_iterator path_end, - Nonnull field_value, - SourceLocation source_loc) -> Nonnull { +static auto SetFieldImpl( + Nonnull arena, Nonnull value, + std::vector::const_iterator path_begin, + std::vector::const_iterator path_end, + Nonnull field_value, SourceLocation source_loc) + -> Nonnull { if (path_begin == path_end) { return field_value; } @@ -109,11 +130,11 @@ static auto SetFieldImpl(Nonnull arena, Nonnull value, std::vector elements = cast(*value).elements(); auto it = std::find_if(elements.begin(), elements.end(), [path_begin](const NamedValue& element) { - return element.name == *path_begin; + return element.name == (*path_begin).name(); }); if (it == elements.end()) { FATAL_RUNTIME_ERROR(source_loc) - << "field " << *path_begin << " not in " << *value; + << "field " << (*path_begin).name() << " not in " << *value; } it->value = SetFieldImpl(arena, it->value, path_begin + 1, path_end, field_value, source_loc); @@ -127,10 +148,10 @@ static auto SetFieldImpl(Nonnull arena, Nonnull value, std::vector> elements = cast(*value).elements(); // TODO(geoffromer): update FieldPath to hold integers as well as strings. - int index = std::stoi(*path_begin); + int index = std::stoi((*path_begin).name()); if (index < 0 || static_cast(index) >= elements.size()) { - FATAL_RUNTIME_ERROR(source_loc) - << "index " << *path_begin << " out of range in " << *value; + FATAL_RUNTIME_ERROR(source_loc) << "index " << (*path_begin).name() + << " out of range in " << *value; } elements[index] = SetFieldImpl(arena, elements[index], path_begin + 1, path_end, field_value, source_loc); @@ -159,8 +180,8 @@ void Value::Print(llvm::raw_ostream& out) const { case Value::Kind::BindingPlaceholderValue: { const auto& placeholder = cast(*this); out << "Placeholder<"; - if (placeholder.named_entity().has_value()) { - out << (*placeholder.named_entity()).name(); + if (placeholder.value_node().has_value()) { + out << (*placeholder.value_node()); } else { out << "_"; } @@ -266,6 +287,17 @@ void Value::Print(llvm::raw_ostream& out) const { out << "class " << class_type.declaration().name(); break; } + case Value::Kind::InterfaceType: { + const InterfaceType& iface_type = cast(*this); + out << "interface " << iface_type.declaration().name(); + break; + } + case Value::Kind::Witness: { + const auto& witness = cast(*this); + out << "impl " << *witness.declaration().impl_type() << " as " + << witness.declaration().interface(); + break; + } case Value::Kind::ChoiceType: out << "choice " << cast(*this).name(); break; @@ -289,6 +321,14 @@ void Value::Print(llvm::raw_ostream& out) const { << cast(*this).class_type().declaration().name() << ")"; break; + case Value::Kind::TypeOfInterfaceType: + out << "typeof(" + << cast(*this) + .interface_type() + .declaration() + .name() + << ")"; + break; case Value::Kind::TypeOfChoiceType: out << "typeof(" << cast(*this).choice_type().name() << ")"; @@ -364,6 +404,9 @@ auto TypeEqual(Nonnull t1, Nonnull t2) -> bool { case Value::Kind::NominalClassType: return cast(*t1).declaration().name() == cast(*t2).declaration().name(); + case Value::Kind::InterfaceType: + return cast(*t1).declaration().name() == + cast(*t2).declaration().name(); case Value::Kind::ChoiceType: return cast(*t1).name() == cast(*t2).name(); case Value::Kind::TupleValue: { @@ -391,13 +434,34 @@ auto TypeEqual(Nonnull t1, Nonnull t2) -> bool { case Value::Kind::TypeOfClassType: return TypeEqual(&cast(*t1).class_type(), &cast(*t2).class_type()); + case Value::Kind::TypeOfInterfaceType: + return TypeEqual(&cast(*t1).interface_type(), + &cast(*t2).interface_type()); case Value::Kind::TypeOfChoiceType: return TypeEqual(&cast(*t1).choice_type(), &cast(*t2).choice_type()); - default: + case Value::Kind::IntValue: + case Value::Kind::BoolValue: + case Value::Kind::FunctionValue: + case Value::Kind::BoundMethodValue: + case Value::Kind::StructValue: + case Value::Kind::NominalClassValue: + case Value::Kind::AlternativeValue: + case Value::Kind::AlternativeConstructorValue: + case Value::Kind::StringValue: + case Value::Kind::PointerValue: + case Value::Kind::LValue: + case Value::Kind::BindingPlaceholderValue: + case Value::Kind::ContinuationValue: FATAL() << "TypeEqual used to compare non-type values\n" << *t1 << "\n" << *t2; + case Value::Kind::Witness: + FATAL() << "TypeEqual: unexpected Witness"; + break; + case Value::Kind::AutoType: + FATAL() << "TypeEqual: unexpected AutoType"; + break; } } @@ -468,11 +532,14 @@ auto ValueEqual(Nonnull v1, Nonnull v2) -> bool { case Value::Kind::AutoType: case Value::Kind::StructType: case Value::Kind::NominalClassType: + case Value::Kind::InterfaceType: + case Value::Kind::Witness: case Value::Kind::ChoiceType: case Value::Kind::ContinuationType: case Value::Kind::VariableType: case Value::Kind::StringType: case Value::Kind::TypeOfClassType: + case Value::Kind::TypeOfInterfaceType: case Value::Kind::TypeOfChoiceType: return TypeEqual(v1, v2); case Value::Kind::NominalClassValue: @@ -533,29 +600,21 @@ auto FieldTypes(const NominalClassType& class_type) -> std::vector { return field_types; } -auto NominalClassType::FindMember(const std::string& name) const +auto FindMember(const std::string& name, + llvm::ArrayRef> members) -> 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; + for (Nonnull member : members) { + if (std::optional mem_name = GetName(*member); + mem_name.has_value()) { + if (*mem_name == name) + return member; } } return std::nullopt; } +void ImplBinding::Print(llvm::raw_ostream& out) const { + out << "impl " << *type_var_ << " as " << *iface_; +} + } // namespace Carbon diff --git a/executable_semantics/interpreter/value.h b/executable_semantics/interpreter/value.h index ba88cac99610..4dd6359ecd28 100644 --- a/executable_semantics/interpreter/value.h +++ b/executable_semantics/interpreter/value.h @@ -44,6 +44,7 @@ class Value { NominalClassValue, AlternativeValue, TupleValue, + Witness, IntType, BoolType, TypeType, @@ -52,6 +53,7 @@ class Value { AutoType, StructType, NominalClassType, + InterfaceType, ChoiceType, ContinuationType, // The type of a continuation. VariableType, // e.g., generic type parameters. @@ -61,6 +63,7 @@ class Value { StringType, StringValue, TypeOfClassType, + TypeOfInterfaceType, TypeOfChoiceType, }; @@ -331,20 +334,20 @@ class BindingPlaceholderValue : public Value { explicit BindingPlaceholderValue() : Value(Kind::BindingPlaceholderValue) {} // Represents a named placeholder. - explicit BindingPlaceholderValue(NamedEntityView named_entity) + explicit BindingPlaceholderValue(ValueNodeView value_node) : Value(Kind::BindingPlaceholderValue), - named_entity_(std::move(named_entity)) {} + value_node_(std::move(value_node)) {} static auto classof(const Value* value) -> bool { return value->kind() == Kind::BindingPlaceholderValue; } - auto named_entity() const -> const std::optional& { - return named_entity_; + auto value_node() const -> const std::optional& { + return value_node_; } private: - std::optional named_entity_; + std::optional value_node_; }; // The int type. @@ -382,11 +385,13 @@ class FunctionType : public Value { public: FunctionType(llvm::ArrayRef> deduced, Nonnull parameters, - Nonnull return_type) + Nonnull return_type, + llvm::ArrayRef> impl_bindings) : Value(Kind::FunctionType), deduced_(deduced), parameters_(parameters), - return_type_(return_type) {} + return_type_(return_type), + impl_bindings_(impl_bindings) {} static auto classof(const Value* value) -> bool { return value->kind() == Kind::FunctionType; @@ -397,11 +402,17 @@ class FunctionType : public Value { } auto parameters() const -> const Value& { return *parameters_; } auto return_type() const -> const Value& { return *return_type_; } + // The bindings for the witness tables (impls) required by the + // bounds on the type parameters of the generic function. + auto impl_bindings() const -> llvm::ArrayRef> { + return impl_bindings_; + } private: std::vector> deduced_; Nonnull parameters_; Nonnull return_type_; + std::vector> impl_bindings_; }; // A pointer type. @@ -463,10 +474,6 @@ class NominalClassType : public Value { 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 @@ -476,6 +483,46 @@ class NominalClassType : public Value { Nonnull declaration_; }; +auto FieldTypes(const NominalClassType&) -> std::vector; +// Return the declaration of the member with the given name. +auto FindMember(const std::string& name, + llvm::ArrayRef> members) + -> std::optional>; + +// An interface type. +class InterfaceType : public Value { + public: + InterfaceType(Nonnull declaration) + : Value(Kind::InterfaceType), declaration_(declaration) {} + + static auto classof(const Value* value) -> bool { + return value->kind() == Kind::InterfaceType; + } + + auto declaration() const -> const InterfaceDeclaration& { + return *declaration_; + } + + private: + Nonnull declaration_; +}; + +// The witness table for an impl. +class Witness : public Value { + public: + Witness(Nonnull declaration) + : Value(Kind::Witness), declaration_(declaration) {} + + static auto classof(const Value* value) -> bool { + return value->kind() == Kind::Witness; + } + + auto declaration() const -> const ImplDeclaration& { return *declaration_; } + + private: + Nonnull declaration_; +}; + auto FieldTypes(const NominalClassType&) -> std::vector; // A choice type. @@ -627,6 +674,21 @@ class TypeOfClassType : public Value { Nonnull class_type_; }; +class TypeOfInterfaceType : public Value { + public: + explicit TypeOfInterfaceType(Nonnull iface_type) + : Value(Kind::TypeOfInterfaceType), iface_type_(iface_type) {} + + static auto classof(const Value* value) -> bool { + return value->kind() == Kind::TypeOfInterfaceType; + } + + auto interface_type() const -> const InterfaceType& { return *iface_type_; } + + private: + Nonnull iface_type_; +}; + // The type of an expression whose value is a choice type. Currently there is no // way to explicitly name such a type in Carbon code, but we are tentatively // using `typeof(ChoiceName)` as the debug-printing format, in anticipation of diff --git a/executable_semantics/syntax/lexer.lpp b/executable_semantics/syntax/lexer.lpp index ae658c5e7bf2..668d93449006 100644 --- a/executable_semantics/syntax/lexer.lpp +++ b/executable_semantics/syntax/lexer.lpp @@ -42,6 +42,7 @@ AMPERSAND "&" AND "and" API "api" ARROW "->" +AS "as" AUTO "auto" AWAIT "__await" BOOL "Bool" @@ -60,12 +61,14 @@ DOUBLE_ARROW "=>" ELSE "else" EQUAL "=" EQUAL_EQUAL "==" +EXTERNAL "external" FALSE "false" FN "fn" FN_TYPE "__Fn" IF "if" IMPL "impl" IMPORT "import" +INTERFACE "interface" LEFT_CURLY_BRACE "{" LEFT_PARENTHESIS "(" LEFT_SQUARE_BRACKET "[" @@ -139,6 +142,7 @@ string_literal \"([^\\\"\n\v\f\r]|\\.)*\" {AND} { return SIMPLE_TOKEN(AND); } {API} { return SIMPLE_TOKEN(API); } {ARROW} { return SIMPLE_TOKEN(ARROW); } +{AS} { return SIMPLE_TOKEN(AS); } {AUTO} { return SIMPLE_TOKEN(AUTO); } {AWAIT} { return SIMPLE_TOKEN(AWAIT); } {BOOL} { return SIMPLE_TOKEN(BOOL); } @@ -157,12 +161,14 @@ string_literal \"([^\\\"\n\v\f\r]|\\.)*\" {ELSE} { return SIMPLE_TOKEN(ELSE); } {EQUAL_EQUAL} { return SIMPLE_TOKEN(EQUAL_EQUAL); } {EQUAL} { return SIMPLE_TOKEN(EQUAL); } +{EXTERNAL} { return SIMPLE_TOKEN(EXTERNAL); } {FALSE} { return SIMPLE_TOKEN(FALSE); } {FN_TYPE} { return SIMPLE_TOKEN(FN_TYPE); } {FN} { return SIMPLE_TOKEN(FN); } {IF} { return SIMPLE_TOKEN(IF); } {IMPL} { return SIMPLE_TOKEN(IMPL); } {IMPORT} { return SIMPLE_TOKEN(IMPORT); } +{INTERFACE} { return SIMPLE_TOKEN(INTERFACE); } {LEFT_CURLY_BRACE} { return SIMPLE_TOKEN(LEFT_CURLY_BRACE); } {LEFT_PARENTHESIS} { return SIMPLE_TOKEN(LEFT_PARENTHESIS); } {LEFT_SQUARE_BRACKET} { return SIMPLE_TOKEN(LEFT_SQUARE_BRACKET); } diff --git a/executable_semantics/syntax/parser.ypp b/executable_semantics/syntax/parser.ypp index 9485f24653ff..f775e3ca365f 100644 --- a/executable_semantics/syntax/parser.ypp +++ b/executable_semantics/syntax/parser.ypp @@ -93,6 +93,7 @@ %token sized_type_literal %token string_literal %type designator +%type impl_kind %type > package_directive %type import_directive %type > import_directives @@ -144,6 +145,7 @@ AND API ARROW + AS AUTO AWAIT BOOL @@ -162,12 +164,14 @@ ELSE EQUAL EQUAL_EQUAL + EXTERNAL FALSE FN FN_TYPE IF IMPL IMPORT + INTERFACE LEFT_CURLY_BRACE LEFT_PARENTHESIS LEFT_SQUARE_BRACKET @@ -742,6 +746,21 @@ declaration: } | VAR variable_declaration EQUAL expression SEMICOLON { $$ = arena->New(context.source_loc(), $2, $4); } +| INTERFACE identifier LEFT_CURLY_BRACE declaration_list RIGHT_CURLY_BRACE + { + auto ty_ty = arena -> New(context.source_loc()); + auto self = + arena -> New(context.source_loc(), "Self", ty_ty); + $$ = arena->New(context.source_loc(), $2, self, $4); + } +| impl_kind IMPL expression AS expression LEFT_CURLY_BRACE declaration_list RIGHT_CURLY_BRACE + { $$ = arena->New(context.source_loc(), $1, $3, $5, $7); } +; +impl_kind: + // Internal + { $$ = Carbon::ImplKind::InternalImpl; } +| EXTERNAL + { $$ = Carbon::ImplKind::ExternalImpl; } ; declaration_list: // Empty diff --git a/executable_semantics/testdata/basic_syntax/trace.carbon b/executable_semantics/testdata/basic_syntax/trace.carbon index 1f717d6424db..fb422e4d7fa1 100644 --- a/executable_semantics/testdata/basic_syntax/trace.carbon +++ b/executable_semantics/testdata/basic_syntax/trace.carbon @@ -13,7 +13,6 @@ // CHECK: fn Print (format_str: String) { // CHECK: ********** type checking ********** // CHECK: checking pattern (format_str: String) -// CHECK: constants: {{.*Main: fun
.*}} // CHECK: ********** type checking complete ********** // CHECK: fn Print (format_str: String) { // CHECK: ********** starting execution ********** diff --git a/executable_semantics/testdata/global_variable/fail_init_order.carbon b/executable_semantics/testdata/global_variable/fail_init_order.carbon index 4ddb24c822bd..7a66ef2678f8 100644 --- a/executable_semantics/testdata/global_variable/fail_init_order.carbon +++ b/executable_semantics/testdata/global_variable/fail_init_order.carbon @@ -7,7 +7,7 @@ // RUN: %{not} %{executable_semantics} --trace %s 2>&1 | \ // RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s // AUTOUPDATE: %{executable_semantics} %s -// CHECK: RUNTIME ERROR: {{.*}}/executable_semantics/testdata/global_variable/fail_init_order.carbon:17: could not find `y` +// CHECK: RUNTIME ERROR: {{.*}}/executable_semantics/testdata/global_variable/fail_init_order.carbon:17: could not find `y: i32` package ExecutableSemanticsTest api; diff --git a/executable_semantics/testdata/interface/external_impl_point_vector.carbon b/executable_semantics/testdata/interface/external_impl_point_vector.carbon new file mode 100644 index 000000000000..96eaaf4d907c --- /dev/null +++ b/executable_semantics/testdata/interface/external_impl_point_vector.carbon @@ -0,0 +1,44 @@ +// 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; + +interface Vector { + fn Add[me: Self](b: Self) -> Self; + fn Scale[me: Self](v: i32) -> Self; +} + +class Point { + var x: i32; + var y: i32; +} + +external impl Point as Vector { + fn Add[me: Point](b: Point) -> Point { + return {.x = me.x + b.x, .y = me.y + b.y}; + } + fn Scale[me: Point](v: i32) -> Point { + return {.x = me.x * v, .y = me.y * v}; + } +} + +fn AddAndScaleGeneric[T:! Vector](a: T, b: T, s: i32) -> T { + var m: auto = a.Add; + var n: auto = m(b).Scale; + return n(s); +} + +fn Main() -> i32 { + var a: Point = {.x = 1, .y = 4}; + var b: Point = {.x = 2, .y = 3}; + var p: Point = AddAndScaleGeneric(a, b, 5); + return p.x - 15; +} diff --git a/executable_semantics/testdata/interface/fail_impl_bad_member.carbon b/executable_semantics/testdata/interface/fail_impl_bad_member.carbon new file mode 100644 index 000000000000..8dede3e341b0 --- /dev/null +++ b/executable_semantics/testdata/interface/fail_impl_bad_member.carbon @@ -0,0 +1,43 @@ +// 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/interface/fail_impl_bad_member.carbon:28: type error in member of implementation: 'fn (i32) -> i32' is not implicitly convertible to 'fn (i32) -> class Point' + +package ExecutableSemanticsTest api; + +interface Vector { + fn Add[me: Self](b: Self) -> Self; + fn Scale[me: Self](v: i32) -> Self; +} + +class Point { + var x: i32; + var y: i32; + impl Point as Vector { + fn Add[me: Point](b: Point) -> Point { + return {.x = me.x + b.x, .y = me.y + b.y}; + } + fn Scale[me: Point](v: i32) -> i32 { + return 0; + } + } +} + +fn AddAndScaleGeneric[T:! Vector](a: T, b: T, s: i32) -> T { + var m: auto = a.Add; + var n: auto = m(b).Scale; + return n(s); +} + +fn Main() -> i32 { + var a: Point = {.x = 0, .y = 0}; + var b: Point = {.x = 2, .y = 3}; + var p: Point = AddAndScaleGeneric(a, b, 3); + return p.x - 6; +} diff --git a/executable_semantics/testdata/interface/fail_impl_missing_member.carbon b/executable_semantics/testdata/interface/fail_impl_missing_member.carbon new file mode 100644 index 000000000000..7cc22788b3dd --- /dev/null +++ b/executable_semantics/testdata/interface/fail_impl_missing_member.carbon @@ -0,0 +1,40 @@ +// 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/interface/fail_impl_missing_member.carbon:26: implementation missing Scale + +package ExecutableSemanticsTest api; + +interface Vector { + fn Add[me: Self](b: Self) -> Self; + fn Scale[me: Self](v: i32) -> Self; +} + +class Point { + var x: i32; + var y: i32; + impl Point as Vector { + fn Add[me: Point](b: Point) -> Point { + return {.x = me.x + b.x, .y = me.y + b.y}; + } + } +} + +fn AddAndScaleGeneric[T:! Vector](a: T, b: T, s: i32) -> T { + var m: auto = a.Add; + var n: auto = m(b).Scale; + return n(s); +} + +fn Main() -> i32 { + var a: Point = {.x = 0, .y = 0}; + var b: Point = {.x = 2, .y = 3}; + var p: Point = AddAndScaleGeneric(a, b, 3); + return p.x - 6; +} diff --git a/executable_semantics/testdata/interface/fail_no_impl.carbon b/executable_semantics/testdata/interface/fail_no_impl.carbon new file mode 100644 index 000000000000..555505a81f79 --- /dev/null +++ b/executable_semantics/testdata/interface/fail_no_impl.carbon @@ -0,0 +1,33 @@ +// 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/interface/fail_no_impl.carbon:31: could not find implementation of interface Vector for class Point + +package ExecutableSemanticsTest api; + +interface Vector { + fn Add[me: Self](b: Self) -> Self; + fn Scale[me: Self](v: i32) -> Self; +} + +class Point { + var x: i32; + var y: i32; +} + +fn AddAndScaleGeneric[T:! Vector](a: T, b: T, s: i32) -> T { + return a.Add(b).Scale(s); +} + +fn Main() -> i32 { + var a: Point = {.x = 0, .y = 0}; + var b: Point = {.x = 2, .y = 3}; + var p: Point = AddAndScaleGeneric(a, b, 3); + return p.x - 6; +} diff --git a/executable_semantics/testdata/interface/generic_call_generic.carbon b/executable_semantics/testdata/interface/generic_call_generic.carbon new file mode 100644 index 000000000000..033233c6e9bb --- /dev/null +++ b/executable_semantics/testdata/interface/generic_call_generic.carbon @@ -0,0 +1,45 @@ +// 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; + +interface Vector { + fn Add[me: Self](b: Self) -> Self; + fn Scale[me: Self](v: i32) -> Self; +} + +class Point { + var x: i32; + var y: i32; + impl Point as Vector { + fn Add[me: Point](b: Point) -> Point { + return {.x = me.x + b.x, .y = me.y + b.y}; + } + fn Scale[me: Point](v: i32) -> Point { + return {.x = me.x * v, .y = me.y * v}; + } + } +} + +fn ScaleGeneric[U:! Vector](c: U, s: i32) -> U { + return c.Scale(s); +} + +fn AddAndScaleGeneric[T:! Vector](a: T, b: T, s: i32) -> T { + return ScaleGeneric(a.Add(b), s); +} + +fn Main() -> i32 { + var a: Point = {.x = 1, .y = 1}; + var b: Point = {.x = 2, .y = 3}; + var p: Point = AddAndScaleGeneric(a, b, 5); + return p.x - 15; +} diff --git a/executable_semantics/testdata/interface/generic_with_two_params.carbon b/executable_semantics/testdata/interface/generic_with_two_params.carbon new file mode 100644 index 000000000000..cba76a7749e7 --- /dev/null +++ b/executable_semantics/testdata/interface/generic_with_two_params.carbon @@ -0,0 +1,59 @@ +// 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; + +interface Vector { + fn Add[me: Self](b: Self) -> Self; + fn Scale[me: Self](v: i32) -> Self; +} + +class Point1 { + var x: i32; + var y: i32; + impl Point1 as Vector { + fn Add[me: Point1](b: Point1) -> Point1 { + return {.x = me.x + b.x, .y = me.y + b.y}; + } + fn Scale[me: Point1](v: i32) -> Point1 { + return {.x = me.x * v, .y = me.y * v}; + } + } +} + +class Point2 { + var x: i32; + var y: i32; + impl Point2 as Vector { + fn Add[me: Point2](b: Point2) -> Point2 { + return {.x = me.x + b.x + 1, .y = me.y + b.y + 1}; + } + fn Scale[me: Point2](v: i32) -> Point2 { + return {.x = me.x * v * 2, .y = me.y * v * 2}; + } + } +} + +fn ScaleGeneric[U:! Vector](c: U, s: i32) -> U { + return c.Scale(s); +} + +fn AddAndScaleGeneric[T:! Vector, V:! Vector](a: T, b: V, s: i32) -> (T,V) { + return (ScaleGeneric(a.Add(a), s), + ScaleGeneric(b.Add(b), s)); +} + +fn Main() -> i32 { + var a: Point1 = {.x = 1, .y = 1}; + var b: Point2 = {.x = 2, .y = 3}; + var (p: Point1, q: Point2) = AddAndScaleGeneric(a, b, 5); + return q.x - p.x - 40; +} diff --git a/executable_semantics/testdata/interface/tuple_vector_add_scale.carbon b/executable_semantics/testdata/interface/tuple_vector_add_scale.carbon new file mode 100644 index 000000000000..e1d06fd8ec75 --- /dev/null +++ b/executable_semantics/testdata/interface/tuple_vector_add_scale.carbon @@ -0,0 +1,43 @@ +// 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; + +interface Vector { + fn Add[me: Self](b: Self) -> Self; + fn Scale[me: Self](v: i32) -> Self; +} + +class Point { + var x: i32; + var y: i32; + impl Point as Vector { + fn Add[me: Point](b: Point) -> Point { + return {.x = me.x + b.x, .y = me.y + b.y}; + } + fn Scale[me: Point](v: i32) -> Point { + return {.x = me.x * v, .y = me.y * v}; + } + } +} + +fn AddAndScaleGeneric[T:! Vector](t: (T, T), s: i32) -> T { + var m: auto = t[0].Add; + var n: auto = m(t[1]).Scale; + return n(s); +} + +fn Main() -> i32 { + var a: Point = {.x = 1, .y = 1}; + var b: Point = {.x = 2, .y = 3}; + var p: Point = AddAndScaleGeneric((a, b), 5); + return p.x - 15; +} diff --git a/executable_semantics/testdata/interface/vector_point_add_scale.carbon b/executable_semantics/testdata/interface/vector_point_add_scale.carbon new file mode 100644 index 000000000000..c86b8c314fa8 --- /dev/null +++ b/executable_semantics/testdata/interface/vector_point_add_scale.carbon @@ -0,0 +1,43 @@ +// 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; + +interface Vector { + fn Add[me: Self](b: Self) -> Self; + fn Scale[me: Self](v: i32) -> Self; +} + +class Point { + var x: i32; + var y: i32; + impl Point as Vector { + fn Add[me: Point](b: Point) -> Point { + return {.x = me.x + b.x, .y = me.y + b.y}; + } + fn Scale[me: Point](v: i32) -> Point { + return {.x = me.x * v, .y = me.y * v}; + } + } +} + +fn AddAndScaleGeneric[T:! Vector](a: T, b: T, s: i32) -> T { + var m: __Fn(T)->T = a.Add; + var n: __Fn(i32)->T = m(b).Scale; + return n(s); +} + +fn Main() -> i32 { + var a: Point = {.x = 1, .y = 1}; + var b: Point = {.x = 2, .y = 3}; + var p: Point = AddAndScaleGeneric(a, b, 5); + return p.x - 15; +} diff --git a/executable_semantics/testdata/tuple/fail_index_var.carbon b/executable_semantics/testdata/tuple/fail_index_var.carbon index ee6a01c3242f..6b3c4a11bb0e 100644 --- a/executable_semantics/testdata/tuple/fail_index_var.carbon +++ b/executable_semantics/testdata/tuple/fail_index_var.carbon @@ -7,7 +7,7 @@ // RUN: %{not} %{executable_semantics} --trace %s 2>&1 | \ // RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s // AUTOUPDATE: %{executable_semantics} %s -// CHECK: RUNTIME ERROR: {{.*}}/executable_semantics/testdata/tuple/fail_index_var.carbon:17: could not find `index` +// CHECK: RUNTIME ERROR: {{.*}}/executable_semantics/testdata/tuple/fail_index_var.carbon:17: could not find `index: i32` package ExecutableSemanticsTest api;