Przeglądaj źródła

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).
Adrien Leravat 3 lat temu
rodzic
commit
563768c6d3

+ 16 - 2
explorer/ast/BUILD

@@ -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 - 0
explorer/ast/ast_rtti.txt

@@ -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 - 0
explorer/ast/element.cpp

@@ -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 - 0
explorer/ast/element.h

@@ -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 - 0
explorer/ast/element_test.cpp

@@ -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
explorer/ast/expression.h

@@ -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_;
 };
 

+ 0 - 78
explorer/ast/member.cpp

@@ -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

+ 0 - 76
explorer/ast/member.h

@@ -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
explorer/interpreter/BUILD

@@ -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
explorer/interpreter/address.h

@@ -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

+ 32 - 31
explorer/interpreter/field_path.h → explorer/interpreter/element_path.h

@@ -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
explorer/interpreter/heap.cpp

@@ -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
explorer/interpreter/interpreter.cpp

@@ -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
explorer/interpreter/type_checker.cpp

@@ -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
explorer/interpreter/value.cpp

@@ -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
explorer/interpreter/value.h

@@ -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.