Explorer: split Member into dedicated classes (#2421)

Features:
* Split `Member` class into 3 dedicated classes covering named, positional, and a new "base class" element

Changes:
* Rename `Member` to `Element` to better reflect the variants covered
* Add `NamedElement`, `PositionalElement`, and `BaseElement` child classes for `Element`
* Split `GetMember` into 3 function variants based on available attributes (index, name, nothing).
* Add some unit tests to provide coverage of core features

Motivation:
This changeset splits Member into (currently 2) classes, as we see the need for more Member variants (base class access needed for #2378, possibly unnamed mixins, ...), which in addition to the current ones, also have significantly different attributes. This will allow supporting more Member types in the future cleanly.

Alternatives considered:
The alternative solution, "one class for positional, named & base class access", would expose unused or unavailable attributes depending on the Member actual type (`index()` only for positional, `name()` only for named, and neither for base class access).
This commit is contained in:
Adrien Leravat
2022-12-09 16:09:50 -08:00
committed by GitHub
parent dd2f7d732c
commit 563768c6d3
16 changed files with 507 additions and 307 deletions
+16 -2
View File
@@ -39,8 +39,8 @@ cc_library(
srcs = [
"bindings.cpp",
"declaration.cpp",
"element.cpp",
"expression.cpp",
"member.cpp",
"pattern.cpp",
"statement.cpp",
],
@@ -48,9 +48,9 @@ cc_library(
"ast.h",
"bindings.h",
"declaration.h",
"element.h",
"expression.h",
"impl_binding.h",
"member.h",
"pattern.h",
"return_term.h",
"statement.h",
@@ -128,6 +128,20 @@ cc_library(
],
)
cc_test(
name = "element_test",
srcs = ["element_test.cpp"],
deps = [
":ast",
":paren_contents",
"//common:gtest_main",
"//explorer/common:arena",
"//explorer/interpreter:action_and_value",
"@com_google_googletest//:gtest",
"@llvm-project//llvm:Support",
],
)
cc_test(
name = "pattern_test",
srcs = ["pattern_test.cpp"],
+4
View File
@@ -82,3 +82,7 @@ abstract class WhereClause : AstNode;
class IsWhereClause : WhereClause;
class EqualsWhereClause : WhereClause;
class RewriteWhereClause : WhereClause;
root class Element;
class NamedElement : Element;
class PositionalElement : Element;
class BaseElement : Element;
+66
View File
@@ -0,0 +1,66 @@
// 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 "explorer/ast/element.h"
#include "common/check.h"
#include "explorer/ast/declaration.h"
namespace Carbon {
NamedElement::NamedElement(Nonnull<const Declaration*> declaration)
: Element(ElementKind::NamedElement), element_(declaration) {}
NamedElement::NamedElement(Nonnull<const NamedValue*> struct_member)
: Element(ElementKind::NamedElement), element_(struct_member) {}
auto NamedElement::IsNamed(std::string_view name) const -> bool {
return this->name() == name;
}
auto NamedElement::name() const -> std::string_view {
if (const auto* decl = element_.dyn_cast<const Declaration*>()) {
return GetName(*decl).value();
} else {
const auto* named_value = element_.dyn_cast<const NamedValue*>();
return named_value->name;
}
}
auto NamedElement::type() const -> const Value& {
if (const auto* decl = element_.dyn_cast<const Declaration*>()) {
return decl->static_type();
} else {
const auto* named_value = element_.dyn_cast<const NamedValue*>();
return *named_value->value;
}
}
auto NamedElement::declaration() const
-> std::optional<Nonnull<const Declaration*>> {
if (const auto* decl = element_.dyn_cast<const Declaration*>()) {
return decl;
}
return std::nullopt;
}
void NamedElement::Print(llvm::raw_ostream& out) const { out << name(); }
// Prints the Element
void PositionalElement::Print(llvm::raw_ostream& out) const {
out << "element #" << index_;
}
// Return whether the element's name matches `name`.
auto PositionalElement::IsNamed(std::string_view /*name*/) const -> bool {
return false;
}
void BaseElement::Print(llvm::raw_ostream& out) const { out << "base class"; }
// Return whether the element's name matches `name`.
auto BaseElement::IsNamed(std::string_view /*name*/) const -> bool {
return false;
}
} // namespace Carbon
+138
View File
@@ -0,0 +1,138 @@
// 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 CARBON_EXPLORER_AST_ELEMENT_H_
#define CARBON_EXPLORER_AST_ELEMENT_H_
#include <optional>
#include <string>
#include <string_view>
#include "explorer/ast/ast_rtti.h"
#include "explorer/common/nonnull.h"
#include "llvm/ADT/PointerUnion.h"
namespace Carbon {
class Declaration;
class Value;
// A NamedValue represents a value with a name, such as a single struct field.
struct NamedValue {
// The field name.
std::string name;
// The field's value.
Nonnull<const Value*> value;
};
// A generic member of a type.
//
// This is can be a named, positional or other type of member.
class Element {
protected:
explicit Element(ElementKind kind) : kind_(kind) {}
public:
virtual ~Element() = default;
// Prints the Member
virtual void Print(llvm::raw_ostream& out) const = 0;
// Return whether the member's name matches `name`.
virtual auto IsNamed(std::string_view name) const -> bool = 0;
// Returns the enumerator corresponding to the most-derived type of this
// object.
auto kind() const -> ElementKind { return kind_; }
// The declared type of the member, which might include type variables.
virtual auto type() const -> const Value& = 0;
private:
const ElementKind kind_;
};
// A named element of a type.
//
// This is either a declared member of a class, interface, or similar, or a
// member of a struct with no declaration.
class NamedElement : public Element {
public:
explicit NamedElement(Nonnull<const Declaration*> declaration);
explicit NamedElement(Nonnull<const NamedValue*> struct_member);
// Prints the element's name
void Print(llvm::raw_ostream& out) const override;
auto IsNamed(std::string_view name) const -> bool override;
static auto classof(const Element* member) -> bool {
return InheritsFromNamedElement(member->kind());
}
auto type() const -> const Value& override;
// The name of the member.
auto name() const -> std::string_view;
// A declaration of the member, if any exists.
auto declaration() const -> std::optional<Nonnull<const Declaration*>>;
private:
const llvm::PointerUnion<Nonnull<const Declaration*>,
Nonnull<const NamedValue*>>
element_;
};
// A positional element of a type.
//
// This is a positional tuple element, or other index-based value.
class PositionalElement : public Element {
public:
explicit PositionalElement(int index, Nonnull<const Value*> type)
: Element(ElementKind::PositionalElement), index_(index), type_(type) {}
// Prints the element
void Print(llvm::raw_ostream& out) const override;
// Return whether the member's name matches `name`.
auto IsNamed(std::string_view name) const -> bool override;
static auto classof(const Element* member) -> bool {
return InheritsFromPositionalElement(member->kind());
}
auto index() const -> int { return index_; }
auto type() const -> const Value& override { return *type_; }
private:
const int index_;
const Nonnull<const Value*> type_;
};
// A base class object.
//
// This is the base class object of a class value.
class BaseElement : public Element {
public:
explicit BaseElement(Nonnull<const Value*> type)
: Element(ElementKind::BaseElement), type_(type) {}
// Prints the Member
void Print(llvm::raw_ostream& out) const override;
// Return whether the member's name matches `name`.
auto IsNamed(std::string_view name) const -> bool override;
static auto classof(const Element* member) -> bool {
return InheritsFromBaseElement(member->kind());
}
auto type() const -> const Value& override { return *type_; }
private:
const Nonnull<const Value*> type_;
};
} // namespace Carbon
#endif // CARBON_EXPLORER_AST_ELEMENT_H_
+96
View File
@@ -0,0 +1,96 @@
// 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 "explorer/ast/element.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <optional>
#include "explorer/ast/bindings.h"
#include "explorer/ast/declaration.h"
#include "explorer/ast/expression.h"
#include "explorer/common/arena.h"
#include "explorer/interpreter/value.h"
#include "llvm/Support/Casting.h"
namespace Carbon::Testing {
namespace {
static auto FakeSourceLoc(int line_num) -> SourceLocation {
return SourceLocation("<test>", line_num);
}
class ElementTest : public ::testing::Test {
protected:
Arena arena;
};
TEST_F(ElementTest, NamedElementType) {
const auto src_loc = FakeSourceLoc(1);
VariableDeclaration decl{
src_loc,
arena.New<BindingPattern>(src_loc, "valuename",
arena.New<AutoPattern>(src_loc),
ValueCategory::Var),
std::nullopt, ValueCategory::Var};
const auto* static_type = arena.New<IntValue>(1);
decl.set_static_type(static_type);
NamedElement element_decl(&decl);
EXPECT_EQ(&element_decl.type(), static_type);
NamedElement named_val(
arena.New<NamedValue>(NamedValue{"valuename", static_type}));
EXPECT_EQ(&named_val.type(), static_type);
}
TEST_F(ElementTest, NamedElementDeclaration) {
const auto src_loc = FakeSourceLoc(1);
VariableDeclaration decl{
src_loc,
arena.New<BindingPattern>(src_loc, "valuename",
arena.New<AutoPattern>(src_loc),
ValueCategory::Var),
std::nullopt, ValueCategory::Var};
const auto* static_type = arena.New<IntValue>(1);
NamedElement element_decl(&decl);
EXPECT_TRUE(element_decl.declaration());
NamedElement named_val(
arena.New<NamedValue>(NamedValue{"valuename", static_type}));
EXPECT_FALSE(named_val.declaration());
}
TEST_F(ElementTest, NamedElementIsNamed) {
const auto src_loc = FakeSourceLoc(1);
VariableDeclaration decl{
src_loc,
arena.New<BindingPattern>(src_loc, "valuename",
arena.New<AutoPattern>(src_loc),
ValueCategory::Var),
std::nullopt, ValueCategory::Var};
NamedElement member_decl(&decl);
EXPECT_TRUE(member_decl.IsNamed("valuename"));
EXPECT_FALSE(member_decl.IsNamed("anything"));
NamedElement named_val(
arena.New<NamedValue>(NamedValue{"valuename", arena.New<IntValue>(1)}));
EXPECT_TRUE(named_val.IsNamed("valuename"));
EXPECT_FALSE(named_val.IsNamed("anything"));
}
TEST_F(ElementTest, PositionalElementIsNamed) {
PositionalElement element(1, arena.New<IntValue>(1));
EXPECT_FALSE(element.IsNamed("anything"));
}
TEST_F(ElementTest, BaseElementIsNamed) {
BaseElement element(arena.New<IntValue>(1));
EXPECT_FALSE(element.IsNamed("anything"));
}
} // namespace
} // namespace Carbon::Testing
+6 -9
View File
@@ -15,7 +15,7 @@
#include "common/ostream.h"
#include "explorer/ast/ast_node.h"
#include "explorer/ast/bindings.h"
#include "explorer/ast/member.h"
#include "explorer/ast/element.h"
#include "explorer/ast/paren_contents.h"
#include "explorer/ast/static_scope.h"
#include "explorer/ast/value_category.h"
@@ -319,18 +319,15 @@ class SimpleMemberAccessExpression : public MemberAccessExpression {
auto member_name() const -> const std::string& { return member_name_; }
// Returns the `Member` that the member name resolved to.
// Returns the `NamedElement` that the member name resolved to.
// Should not be called before typechecking.
auto member() const -> const Member& {
auto member() const -> const NamedElement& {
CARBON_CHECK(member_.has_value());
return *member_;
return *member_.value();
}
// Can only be called once, during typechecking.
void set_member(Member member) {
CARBON_CHECK(!member_.has_value());
member_ = member;
}
void set_member(Nonnull<const NamedElement*> member) { member_ = member; }
// If `object` is a constrained type parameter and `member` was found in an
// interface, returns that interface. Should not be called before
@@ -348,7 +345,7 @@ class SimpleMemberAccessExpression : public MemberAccessExpression {
private:
std::string member_name_;
std::optional<Member> member_;
std::optional<Nonnull<const NamedElement*>> member_;
std::optional<Nonnull<const InterfaceType*>> found_in_interface_;
};
-78
View File
@@ -1,78 +0,0 @@
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include "explorer/ast/member.h"
#include "common/check.h"
#include "explorer/ast/declaration.h"
namespace Carbon {
Member::Member(Nonnull<const Declaration*> declaration)
: member_(declaration) {}
Member::Member(Nonnull<const NamedValue*> struct_member)
: member_(struct_member) {}
Member::Member(Nonnull<const IndexedValue*> tuple_member)
: member_(tuple_member) {}
auto Member::IsNamed(std::string_view other_name) const -> bool {
return HasName() && name() == other_name;
}
auto Member::name() const -> std::string_view {
CARBON_CHECK(HasName()) << "Unnamed member does not have a name()";
if (const auto* decl = member_.dyn_cast<const Declaration*>()) {
return GetName(*decl).value();
} else if (const auto* named_valued = member_.dyn_cast<const NamedValue*>()) {
return named_valued->name;
} else {
CARBON_FATAL() << "Unreachable";
}
}
auto Member::HasPosition() const -> bool {
return member_.dyn_cast<const IndexedValue*>() != nullptr;
}
auto Member::HasName() const -> bool {
// Both are currently mutually exclusive
return !HasPosition();
}
auto Member::index() const -> int {
CARBON_CHECK(HasPosition())
<< "Non-positional member does not have an index()";
return member_.dyn_cast<const IndexedValue*>()->index;
}
auto Member::type() const -> const Value& {
if (const auto* decl = member_.dyn_cast<const Declaration*>()) {
return decl->static_type();
} else if (const auto* named_valued = member_.dyn_cast<const NamedValue*>()) {
return *named_valued->value;
} else {
return *member_.get<const IndexedValue*>()->value;
}
}
auto Member::declaration() const -> std::optional<Nonnull<const Declaration*>> {
if (const auto* decl = member_.dyn_cast<const Declaration*>()) {
return decl;
}
return std::nullopt;
}
void Member::Print(llvm::raw_ostream& out) const {
if (HasName()) {
out << name();
} else if (const auto* value = member_.dyn_cast<const IndexedValue*>()) {
out << "element #" << member_.get<const IndexedValue*>()->index;
} else {
CARBON_FATAL() << "Unhandled member type";
}
}
} // namespace Carbon
-76
View File
@@ -1,76 +0,0 @@
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#ifndef CARBON_EXPLORER_AST_MEMBER_H_
#define CARBON_EXPLORER_AST_MEMBER_H_
#include <optional>
#include <string>
#include <string_view>
#include "explorer/common/nonnull.h"
#include "llvm/ADT/PointerUnion.h"
namespace Carbon {
class Declaration;
class Value;
// A NamedValue represents a value with a name, such as a single struct field.
struct NamedValue {
// The field name.
std::string name;
// The field's value.
Nonnull<const Value*> value;
};
// A IndexedValue represents a value identified by an index, such as a tuple
// field.
struct IndexedValue {
// The field index.
int index;
// The field's value.
Nonnull<const Value*> value;
};
// A member of a type.
//
// This is either a declared member of a class, interface, or similar, or a
// member of a struct with no declaration.
class Member {
public:
explicit Member(Nonnull<const Declaration*> declaration);
explicit Member(Nonnull<const NamedValue*> struct_member);
explicit Member(Nonnull<const IndexedValue*> tuple_member);
// Return whether the member's name matches `name`.
auto IsNamed(std::string_view name) const -> bool;
// Prints the Member
void Print(llvm::raw_ostream& out) const;
// Return whether the member is positional, i.e. has an index.
auto HasPosition() const -> bool;
// Return whether the member is named, i.e. has a name.
auto HasName() const -> bool;
// The name of the member. Requires *this to represent a named member.
auto name() const -> std::string_view;
// The index of the member. Requires *this to represent a positional member.
auto index() const -> int;
// The declared type of the member, which might include type variables.
auto type() const -> const Value&;
// A declaration of the member, if any exists.
auto declaration() const -> std::optional<Nonnull<const Declaration*>>;
private:
llvm::PointerUnion<Nonnull<const Declaration*>, Nonnull<const NamedValue*>,
Nonnull<const IndexedValue*>>
member_;
};
} // namespace Carbon
#endif // CARBON_EXPLORER_AST_MEMBER_H_
+6 -4
View File
@@ -16,10 +16,12 @@ cc_library(
"action.h",
"value.h",
],
# Exposed to resolve `member_test` dependencies.
visibility = ["//explorer/ast:__pkg__"],
deps = [
":address",
":dictionary",
":field_path",
":element_path",
":heap_allocation_interface",
":stack",
"//common:check",
@@ -51,7 +53,7 @@ cc_library(
name = "address",
hdrs = ["address.h"],
deps = [
":field_path",
":element_path",
"//common:ostream",
"@llvm-project//llvm:Support",
],
@@ -86,8 +88,8 @@ cc_library(
)
cc_library(
name = "field_path",
hdrs = ["field_path.h"],
name = "element_path",
hdrs = ["element_path.h"],
deps = [
"//common:ostream",
"//explorer/ast",
+6 -6
View File
@@ -10,7 +10,7 @@
#include <vector>
#include "common/ostream.h"
#include "explorer/interpreter/field_path.h"
#include "explorer/interpreter/element_path.h"
#include "llvm/Support/Compiler.h"
namespace Carbon {
@@ -57,18 +57,18 @@ class Address {
// Prints a human-readable representation of `a` to `out`.
//
// Currently, that representation consists of an AllocationId followed by an
// optional FieldPath specifying a particular field within that allocation.
// optional ElementPath specifying a particular field within that allocation.
void Print(llvm::raw_ostream& out) const {
out << allocation_ << field_path_;
out << allocation_ << element_path_;
}
LLVM_DUMP_METHOD void Dump() const { Print(llvm::errs()); }
// If *this represents the address of an object with a field named
// `field_name`, this method returns the address of that field.
auto SubobjectAddress(Member member) const -> Address {
auto ElementAddress(Nonnull<const Element*> element) const -> Address {
Address result = *this;
result.field_path_.Append(member);
result.element_path_.Append(element);
return result;
}
@@ -79,7 +79,7 @@ class Address {
friend class Heap;
AllocationId allocation_;
FieldPath field_path_;
ElementPath element_path_;
};
} // namespace Carbon
@@ -2,8 +2,8 @@
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#ifndef CARBON_EXPLORER_INTERPRETER_FIELD_PATH_H_
#define CARBON_EXPLORER_INTERPRETER_FIELD_PATH_H_
#ifndef CARBON_EXPLORER_INTERPRETER_ELEMENT_PATH_H_
#define CARBON_EXPLORER_INTERPRETER_ELEMENT_PATH_H_
#include <optional>
#include <string>
@@ -11,7 +11,7 @@
#include <vector>
#include "common/ostream.h"
#include "explorer/ast/member.h"
#include "explorer/ast/element.h"
#include "explorer/ast/static_scope.h"
#include "llvm/Support/Compiler.h"
@@ -20,38 +20,38 @@ namespace Carbon {
class InterfaceType;
class Witness;
// Given some initial Value, a FieldPath identifies a sub-Value within it,
// Given some initial Value, a ElementPath 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
// Value is specified by the context in which the FieldPath is used, not
// by the FieldPath itself.
// Value is specified by the context in which the ElementPath is used, not
// by the ElementPath itself.
//
// A FieldPath consists of a series of steps, which specify how to
// A ElementPath consists of a series of steps, which specify how to
// incrementally navigate from a Value to one of its fields. Currently
// there is only one kind of step, a string specifying a child field by name,
// but that may change as Carbon develops. Note that an empty FieldPath
// but that may change as Carbon develops. Note that an empty ElementPath
// refers to the initial Value itself.
class FieldPath {
class ElementPath {
public:
// Constructs an empty FieldPath.
FieldPath() = default;
// Constructs an empty ElementPath.
ElementPath() = default;
// A single component of the FieldPath, which is typically the name
// A single component of the ElementPath, 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(Member member) : member_(member) {}
Component(Member member,
explicit Component(Nonnull<const Element*> element) : element_(element) {}
Component(Nonnull<const Element*> element,
std::optional<Nonnull<const InterfaceType*>> interface,
std::optional<Nonnull<const Witness*>> witness)
: member_(member), interface_(interface), witness_(witness) {}
: element_(element), interface_(interface), witness_(witness) {}
auto member() const -> Member { return member_; }
auto element() const -> Nonnull<const Element*> { return element_; }
auto IsNamed(std::string_view name) const -> bool {
return member_.IsNamed(name);
return element_->IsNamed(name);
}
auto interface() const -> std::optional<Nonnull<const InterfaceType*>> {
@@ -62,29 +62,30 @@ class FieldPath {
return witness_;
}
void Print(llvm::raw_ostream& out) const { return member_.Print(out); }
void Print(llvm::raw_ostream& out) const { return element_->Print(out); }
private:
Member member_;
Nonnull<const Element*> element_;
std::optional<Nonnull<const InterfaceType*>> interface_;
std::optional<Nonnull<const Witness*>> witness_;
};
// Constructs a FieldPath consisting of a single step.
explicit FieldPath(Member member) : components_({Component(member)}) {}
explicit FieldPath(const Component& f) : components_({f}) {}
// Constructs a ElementPath consisting of a single step.
explicit ElementPath(Nonnull<const Element*> element)
: components_({Component(element)}) {}
explicit ElementPath(const Component& f) : components_({f}) {}
FieldPath(const FieldPath&) = default;
FieldPath(FieldPath&&) = default;
auto operator=(const FieldPath&) -> FieldPath& = default;
auto operator=(FieldPath&&) -> FieldPath& = default;
ElementPath(const ElementPath&) = default;
ElementPath(ElementPath&&) = default;
auto operator=(const ElementPath&) -> ElementPath& = default;
auto operator=(ElementPath&&) -> ElementPath& = default;
// Returns whether *this is empty.
auto IsEmpty() const -> bool { return components_.empty(); }
// Appends `member` to the end of *this.
auto Append(Member member) -> void {
components_.push_back(Component(member));
// Appends `element` to the end of *this.
auto Append(Nonnull<const Element*> element) -> void {
components_.push_back(Component(element));
}
void Print(llvm::raw_ostream& out) const {
@@ -96,7 +97,7 @@ class FieldPath {
LLVM_DUMP_METHOD void Dump() const { Print(llvm::errs()); }
private:
// The representation of FieldPath describes how to locate a Value within
// The representation of ElementPath describes how to locate a Value within
// another Value, so its implementation details are tied to the implementation
// details of Value.
friend class Value;
@@ -105,4 +106,4 @@ class FieldPath {
} // namespace Carbon
#endif // CARBON_EXPLORER_INTERPRETER_FIELD_PATH_H_
#endif // CARBON_EXPLORER_INTERPRETER_ELEMENT_PATH_H_
+2 -2
View File
@@ -31,7 +31,7 @@ auto Heap::Read(const Address& a, SourceLocation source_loc) const
CARBON_RETURN_IF_ERROR(this->CheckInit(a.allocation_, source_loc));
CARBON_RETURN_IF_ERROR(this->CheckAlive(a.allocation_, source_loc));
Nonnull<const Value*> value = values_[a.allocation_.index_];
return value->GetMember(arena_, a.field_path_, source_loc, value);
return value->GetElement(arena_, a.element_path_, source_loc, value);
}
auto Heap::Write(const Address& a, Nonnull<const Value*> v,
@@ -42,7 +42,7 @@ auto Heap::Write(const Address& a, Nonnull<const Value*> v,
}
CARBON_ASSIGN_OR_RETURN(values_[a.allocation_.index_],
values_[a.allocation_.index_]->SetField(
arena_, a.field_path_, v, source_loc));
arena_, a.element_path_, v, source_loc));
return Success();
}
+14 -14
View File
@@ -16,6 +16,7 @@
#include "common/check.h"
#include "explorer/ast/declaration.h"
#include "explorer/ast/element.h"
#include "explorer/ast/expression.h"
#include "explorer/common/arena.h"
#include "explorer/common/error_builders.h"
@@ -423,7 +424,7 @@ auto Interpreter::StepLvalue() -> ErrorOr<Success> {
// { v :: [].f :: C, E, F} :: S, H}
// -> { { &v.f :: C, E, F} :: S, H }
Address object = cast<LValue>(*act.results()[0]).address();
Address member = object.SubobjectAddress(access.member());
Address member = object.ElementAddress(&access.member());
return todo_.FinishAction(arena_->New<LValue>(member));
}
}
@@ -445,7 +446,7 @@ auto Interpreter::StepLvalue() -> ErrorOr<Success> {
Convert(act.results()[0], *access.member().base_type(),
exp.source_loc()));
Address object = cast<LValue>(*val).address();
Address field = object.SubobjectAddress(access.member().member());
Address field = object.ElementAddress(&access.member().member());
return todo_.FinishAction(arena_->New<LValue>(field));
}
}
@@ -464,9 +465,8 @@ auto Interpreter::StepLvalue() -> ErrorOr<Success> {
// -> { { &v[i] :: C, E, F} :: S, H }
Address object = cast<LValue>(*act.results()[0]).address();
const auto index = cast<IntValue>(*act.results()[1]).value();
auto* tuple_field =
arena_->New<IndexedValue>(IndexedValue{index, &exp.static_type()});
Address field = object.SubobjectAddress(Member(tuple_field));
Address field = object.ElementAddress(
arena_->New<PositionalElement>(index, &exp.static_type()));
return todo_.FinishAction(arena_->New<LValue>(field));
}
}
@@ -1131,8 +1131,8 @@ auto Interpreter::StepExp() -> ErrorOr<Success> {
if (access.impl().has_value()) {
witness = cast<Witness>(act.results()[1]);
}
FieldPath::Component member(access.member(), found_in_interface,
witness);
ElementPath::Component member(&access.member(), found_in_interface,
witness);
const Value* aggregate;
if (access.is_type_access()) {
CARBON_ASSIGN_OR_RETURN(
@@ -1147,8 +1147,8 @@ auto Interpreter::StepExp() -> ErrorOr<Success> {
}
CARBON_ASSIGN_OR_RETURN(
Nonnull<const Value*> member_value,
aggregate->GetMember(arena_, FieldPath(member), exp.source_loc(),
act.results()[0]));
aggregate->GetElement(arena_, ElementPath(member),
exp.source_loc(), act.results()[0]));
return todo_.FinishAction(member_value);
}
}
@@ -1216,11 +1216,11 @@ auto Interpreter::StepExp() -> ErrorOr<Success> {
object, Convert(object, *access.member().base_type(),
exp.source_loc()));
}
FieldPath::Component field(access.member().member(),
found_in_interface, witness);
ElementPath::Component field(&access.member().member(),
found_in_interface, witness);
CARBON_ASSIGN_OR_RETURN(Nonnull<const Value*> member,
object->GetMember(arena_, FieldPath(field),
exp.source_loc(), object));
object->GetElement(arena_, ElementPath(field),
exp.source_loc(), object));
return todo_.FinishAction(member);
}
}
@@ -2038,7 +2038,7 @@ auto Interpreter::StepDestroy() -> ErrorOr<Success> {
const auto& member = class_dec.members()[index];
if (const auto* var = dyn_cast<VariableDeclaration>(member)) {
Address object = destroy_act.lvalue()->address();
Address mem = object.SubobjectAddress(Member(var));
Address mem = object.ElementAddress(arena_->New<NamedElement>(var));
SourceLocation source_loc("destructor", 1);
auto v = heap_.Read(mem, source_loc);
return todo_.Spawn(
+37 -29
View File
@@ -2408,19 +2408,26 @@ static void RewriteMemberAccess(Nonnull<MemberAccessExpression*> access,
}
// Determine whether the given member declaration declares an instance member.
static auto IsInstanceMember(Member member) {
if (!member.declaration()) {
// This is a struct field.
return true;
}
Nonnull<const Declaration*> declaration = *member.declaration();
switch (declaration->kind()) {
case DeclarationKind::FunctionDeclaration:
return cast<FunctionDeclaration>(declaration)->is_method();
case DeclarationKind::VariableDeclaration:
static auto IsInstanceMember(Nonnull<const Element*> element) {
switch (element->kind()) {
case ElementKind::BaseElement:
case ElementKind::PositionalElement:
return true;
default:
return false;
case ElementKind::NamedElement:
const auto nom_element = cast<NamedElement>(element);
if (!nom_element->declaration()) {
// This is a struct field.
return true;
}
Nonnull<const Declaration*> declaration = *nom_element->declaration();
switch (declaration->kind()) {
case DeclarationKind::FunctionDeclaration:
return cast<FunctionDeclaration>(declaration)->is_method();
case DeclarationKind::VariableDeclaration:
return true;
default:
return false;
}
}
}
@@ -2555,7 +2562,7 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
const auto& struct_type = cast<StructType>(object_type);
for (const auto& field : struct_type.fields()) {
if (access.member_name() == field.name) {
access.set_member(Member(&field));
access.set_member(arena_->New<NamedElement>(&field));
access.set_static_type(field.value);
access.set_value_category(access.object().value_category());
return Success();
@@ -2574,9 +2581,9 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
auto [member_type, member, member_t_class] = res.value();
Nonnull<const Value*> field_type =
Substitute(member_t_class->bindings(), member_type);
access.set_member(Member(member));
access.set_member(arena_->New<NamedElement>(member));
access.set_static_type(field_type);
access.set_is_type_access(!IsInstanceMember(access.member()));
access.set_is_type_access(!IsInstanceMember(&access.member()));
switch (member->kind()) {
case DeclarationKind::VariableDeclaration:
access.set_value_category(access.object().value_category());
@@ -2647,9 +2654,9 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
const Value& member_type = result.member->static_type();
Nonnull<const Value*> inst_member_type =
Substitute(bindings, &member_type);
access.set_member(Member(result.member));
access.set_member(arena_->New<NamedElement>(result.member));
access.set_found_in_interface(result.interface);
access.set_is_type_access(!IsInstanceMember(access.member()));
access.set_is_type_access(!IsInstanceMember(&access.member()));
access.set_static_type(inst_member_type);
if (auto* func_decl = dyn_cast<FunctionDeclaration>(result.member)) {
@@ -2700,17 +2707,17 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
CARBON_ASSIGN_OR_RETURN(Nonnull<const Witness*> impl,
impl_scope.Resolve(result.interface, type,
e->source_loc(), *this));
access.set_member(Member(result.member));
access.set_member(arena_->New<NamedElement>(result.member));
access.set_impl(impl);
access.set_found_in_interface(result.interface);
if (IsInstanceMember(access.member())) {
if (IsInstanceMember(&access.member())) {
// This is a member name denoting an instance member.
// TODO: Consider setting the static type of all instance member
// declarations to be member name types, rather than special-casing
// member accesses that name them.
access.set_static_type(
arena_->New<TypeOfMemberName>(Member(result.member)));
arena_->New<TypeOfMemberName>(NamedElement(result.member)));
access.set_value_category(ValueCategory::Let);
} else {
// This is a non-instance member whose value is found directly via
@@ -2738,9 +2745,9 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
case Value::Kind::StructType: {
for (const auto& field : cast<StructType>(type)->fields()) {
if (access.member_name() == field.name) {
access.set_member(Member(&field));
access.set_member(arena_->New<NamedElement>(&field));
access.set_static_type(
arena_->New<TypeOfMemberName>(Member(&field)));
arena_->New<TypeOfMemberName>(NamedElement(&field)));
access.set_value_category(ValueCategory::Let);
return Success();
}
@@ -2769,8 +2776,9 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
substituted_parameter_type, &choice);
// TODO: Should there be a Declaration corresponding to each
// choice type alternative?
access.set_member(Member(arena_->New<NamedValue>(
NamedValue{access.member_name(), type})));
access.set_member(
arena_->New<NamedElement>(arena_->New<NamedValue>(
NamedValue{access.member_name(), type})));
access.set_static_type(type);
access.set_value_category(ValueCategory::Let);
return Success();
@@ -2784,7 +2792,7 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
&class_type));
if (type_member.has_value()) {
auto [member_type, member] = type_member.value();
access.set_member(Member(member));
access.set_member(arena_->New<NamedElement>(member));
switch (member->kind()) {
case DeclarationKind::FunctionDeclaration: {
const auto& func = cast<FunctionDeclaration>(*member);
@@ -2801,7 +2809,7 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
break;
}
access.set_static_type(
arena_->New<TypeOfMemberName>(Member(member)));
arena_->New<TypeOfMemberName>(NamedElement(member)));
access.set_value_category(ValueCategory::Let);
return Success();
} else {
@@ -2817,10 +2825,10 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
ConstraintLookupResult result,
LookupInConstraint(e->source_loc(), "member access", type,
access.member_name()));
access.set_member(Member(result.member));
access.set_member(arena_->New<NamedElement>(result.member));
access.set_found_in_interface(result.interface);
access.set_static_type(
arena_->New<TypeOfMemberName>(Member(result.member)));
arena_->New<TypeOfMemberName>(NamedElement(result.member)));
access.set_value_category(ValueCategory::Let);
return Success();
}
@@ -2853,7 +2861,7 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
InterpExp(&access.path(), arena_, trace_stream_));
const auto& member_name = cast<MemberName>(*member_name_value);
access.set_member(&member_name);
bool is_instance_member = IsInstanceMember(member_name.member());
bool is_instance_member = IsInstanceMember(&member_name.member());
bool has_instance = true;
std::optional<Nonnull<const Value*>> base_type = member_name.base_type();
+69 -40
View File
@@ -11,10 +11,11 @@
#include "common/check.h"
#include "common/error.h"
#include "explorer/ast/declaration.h"
#include "explorer/ast/element.h"
#include "explorer/common/arena.h"
#include "explorer/common/error_builders.h"
#include "explorer/interpreter/action.h"
#include "explorer/interpreter/field_path.h"
#include "explorer/interpreter/element_path.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/Casting.h"
@@ -49,39 +50,48 @@ static auto FindClassField(Nonnull<const NominalClassValue*> object,
return std::nullopt;
}
static auto GetPositionalMember(Nonnull<const Value*> v,
const FieldPath::Component& field,
SourceLocation source_loc)
static auto GetBaseElement(Nonnull<const NominalClassValue*> class_value,
SourceLocation source_loc)
-> ErrorOr<Nonnull<const Value*>> {
switch (v->kind()) {
case Value::Kind::TupleValue: {
const auto& tuple = cast<TupleValue>(*v);
const auto index = field.member().index();
if (index < 0 || index >= static_cast<int>(tuple.elements().size())) {
return ProgramError(source_loc)
<< "index " << index << " out of range for " << *v;
}
return tuple.elements()[index];
}
default:
return ProgramError(source_loc)
<< "Invalid positional argument for value " << *v;
const auto base = cast<NominalClassValue>(class_value)->base();
if (!base.has_value()) {
return ProgramError(source_loc)
<< "Non-existent base class for " << *class_value;
}
return base.value();
}
static auto GetNamedMember(Nonnull<Arena*> arena, Nonnull<const Value*> v,
const FieldPath::Component& field,
SourceLocation source_loc,
Nonnull<const Value*> me_value)
static auto GetPositionalElement(Nonnull<const TupleValue*> tuple,
const ElementPath::Component& path_comp,
SourceLocation source_loc)
-> ErrorOr<Nonnull<const Value*>> {
const auto f = field.member().name();
CARBON_CHECK(path_comp.element()->kind() == ElementKind::PositionalElement)
<< "Invalid non-tuple member";
const auto* tuple_element = cast<PositionalElement>(path_comp.element());
const size_t index = tuple_element->index();
if (index < 0 || index >= tuple->elements().size()) {
return ProgramError(source_loc)
<< "index " << index << " out of range for " << *tuple;
}
return tuple->elements()[index];
}
static auto GetNamedElement(Nonnull<Arena*> arena, Nonnull<const Value*> v,
const ElementPath::Component& field,
SourceLocation source_loc,
Nonnull<const Value*> me_value)
-> ErrorOr<Nonnull<const Value*>> {
CARBON_CHECK(field.element()->kind() == ElementKind::NamedElement)
<< "Invalid element, expecting NamedElement";
const auto* member = cast<NamedElement>(field.element());
const auto f = member->name();
if (field.witness().has_value()) {
const auto* witness = cast<Witness>(*field.witness());
// Associated constants.
if (const auto* assoc_const =
dyn_cast_or_null<AssociatedConstantDeclaration>(
field.member().declaration().value_or(nullptr))) {
member->declaration().value_or(nullptr))) {
CARBON_CHECK(field.interface()) << "have witness but no interface";
// TODO: Use witness to find the value of the constant.
return arena->New<AssociatedConstant>(v, *field.interface(), assoc_const,
@@ -169,35 +179,50 @@ static auto GetNamedMember(Nonnull<Arena*> arena, Nonnull<const Value*> v,
&class_type.bindings());
}
default:
CARBON_FATAL() << "field access not allowed for value " << *v;
CARBON_FATAL() << "named element access not supported for value " << *v;
}
}
static auto GetMember(Nonnull<Arena*> arena, Nonnull<const Value*> v,
const FieldPath::Component& field,
SourceLocation source_loc, Nonnull<const Value*> me_value)
static auto GetElement(Nonnull<Arena*> arena, Nonnull<const Value*> v,
const ElementPath::Component& path_comp,
SourceLocation source_loc,
Nonnull<const Value*> me_value)
-> ErrorOr<Nonnull<const Value*>> {
return field.member().HasPosition()
? GetPositionalMember(v, field, source_loc)
: GetNamedMember(arena, v, field, source_loc, me_value);
switch (path_comp.element()->kind()) {
case ElementKind::NamedElement:
return GetNamedElement(arena, v, path_comp, source_loc, me_value);
case ElementKind::PositionalElement: {
if (const auto* tuple = dyn_cast<TupleValue>(v)) {
return GetPositionalElement(tuple, path_comp, source_loc);
} else {
CARBON_FATAL() << "Invalid value for positional element";
}
}
case ElementKind::BaseElement:
if (const auto* class_value = dyn_cast<NominalClassValue>(v)) {
return GetBaseElement(class_value, source_loc);
} else {
CARBON_FATAL() << "Invalid value for base element";
}
}
}
auto Value::GetMember(Nonnull<Arena*> arena, const FieldPath& path,
SourceLocation source_loc,
Nonnull<const Value*> me_value) const
auto Value::GetElement(Nonnull<Arena*> arena, const ElementPath& path,
SourceLocation source_loc,
Nonnull<const Value*> me_value) const
-> ErrorOr<Nonnull<const Value*>> {
Nonnull<const Value*> value(this);
for (const FieldPath::Component& field : path.components_) {
for (const ElementPath::Component& field : path.components_) {
CARBON_ASSIGN_OR_RETURN(
value, Carbon::GetMember(arena, value, field, source_loc, me_value));
value, Carbon::GetElement(arena, value, field, source_loc, me_value));
}
return value;
}
static auto SetFieldImpl(
Nonnull<Arena*> arena, Nonnull<const Value*> value,
std::vector<FieldPath::Component>::const_iterator path_begin,
std::vector<FieldPath::Component>::const_iterator path_end,
std::vector<ElementPath::Component>::const_iterator path_begin,
std::vector<ElementPath::Component>::const_iterator path_end,
Nonnull<const Value*> field_value, SourceLocation source_loc)
-> ErrorOr<Nonnull<const Value*>> {
if (path_begin == path_end) {
@@ -241,10 +266,14 @@ static auto SetFieldImpl(
}
case Value::Kind::TupleType:
case Value::Kind::TupleValue: {
CARBON_CHECK((*path_begin).element()->kind() ==
ElementKind::PositionalElement)
<< "Invalid non-positional member for tuple";
std::vector<Nonnull<const Value*>> elements =
cast<TupleValueBase>(*value).elements();
const auto index = (*path_begin).member().index();
if (index < 0 || index >= static_cast<int>(elements.size())) {
const size_t index =
cast<PositionalElement>((*path_begin).element())->index();
if (index < 0 || index >= elements.size()) {
return ProgramError(source_loc)
<< "index " << index << " out of range in " << *value;
}
@@ -262,7 +291,7 @@ static auto SetFieldImpl(
}
}
auto Value::SetField(Nonnull<Arena*> arena, const FieldPath& path,
auto Value::SetField(Nonnull<Arena*> arena, const ElementPath& path,
Nonnull<const Value*> field_value,
SourceLocation source_loc) const
-> ErrorOr<Nonnull<const Value*>> {
+15 -16
View File
@@ -13,11 +13,11 @@
#include "common/ostream.h"
#include "explorer/ast/bindings.h"
#include "explorer/ast/declaration.h"
#include "explorer/ast/member.h"
#include "explorer/ast/element.h"
#include "explorer/ast/statement.h"
#include "explorer/common/nonnull.h"
#include "explorer/interpreter/address.h"
#include "explorer/interpreter/field_path.h"
#include "explorer/interpreter/element_path.h"
#include "explorer/interpreter/stack.h"
#include "llvm/Support/Compiler.h"
@@ -90,18 +90,18 @@ class Value {
void Print(llvm::raw_ostream& out) const;
LLVM_DUMP_METHOD void Dump() const { Print(llvm::errs()); }
// Returns the sub-Value specified by `path`, which must be a valid field
// Returns the sub-Value specified by `path`, which must be a valid element
// path for *this. If the sub-Value is a method and its self_pattern is an
// AddrPattern, then pass the LValue representing the receiver as `me_value`,
// otherwise pass `*this`.
auto GetMember(Nonnull<Arena*> arena, const FieldPath& path,
SourceLocation source_loc,
Nonnull<const Value*> me_value) const
auto GetElement(Nonnull<Arena*> arena, const ElementPath& path,
SourceLocation source_loc,
Nonnull<const Value*> me_value) const
-> ErrorOr<Nonnull<const Value*>>;
// Returns a copy of *this, but with the sub-Value specified by `path`
// set to `field_value`. `path` must be a valid field path for *this.
auto SetField(Nonnull<Arena*> arena, const FieldPath& path,
auto SetField(Nonnull<Arena*> arena, const ElementPath& path,
Nonnull<const Value*> field_value,
SourceLocation source_loc) const
-> ErrorOr<Nonnull<const Value*>>;
@@ -1139,14 +1139,13 @@ class MemberName : public Value {
public:
MemberName(std::optional<Nonnull<const Value*>> base_type,
std::optional<Nonnull<const InterfaceType*>> interface,
Member member)
NamedElement member)
: Value(Kind::MemberName),
base_type_(base_type),
interface_(interface),
member_(member) {
member_(std::move(member)) {
CARBON_CHECK(base_type || interface)
<< "member name must be in a type, an interface, or both";
CARBON_CHECK(member_.HasName()) << "member must have a name";
}
static auto classof(const Value* value) -> bool {
@@ -1165,14 +1164,14 @@ class MemberName : public Value {
return interface_;
}
// The member.
auto member() const -> Member { return member_; }
auto member() const -> const NamedElement& { return member_; }
// The name of the member.
auto name() const -> std::string_view { return member().name(); }
private:
std::optional<Nonnull<const Value*>> base_type_;
std::optional<Nonnull<const InterfaceType*>> interface_;
Member member_;
NamedElement member_;
};
// A symbolic value representing an associated constant.
@@ -1340,8 +1339,8 @@ class TypeOfParameterizedEntityName : public Value {
// as the member name in a compound member access.
class TypeOfMemberName : public Value {
public:
explicit TypeOfMemberName(Member member)
: Value(Kind::TypeOfMemberName), member_(member) {}
explicit TypeOfMemberName(NamedElement member)
: Value(Kind::TypeOfMemberName), member_(std::move(member)) {}
static auto classof(const Value* value) -> bool {
return value->kind() == Kind::TypeOfMemberName;
@@ -1349,10 +1348,10 @@ class TypeOfMemberName : public Value {
// TODO: consider removing this or moving it elsewhere in the AST,
// since it's arguably part of the expression value rather than its type.
auto member() const -> Member { return member_; }
auto member() const -> NamedElement { return member_; }
private:
Member member_;
NamedElement member_;
};
// The type of a statically-sized array.