Przeglądaj źródła

If a name is not found in a class, perform lookup into base classes. (#3502)

This builds out a little infrastructure for one name scope to `extend`
another. We'll need more refinement here to cover other cases, but this
should provide some foundation for that future work.

---------

Co-authored-by: Jon Ross-Perkins <jperkins@google.com>
Richard Smith 2 lat temu
rodzic
commit
de0c02ddae
28 zmienionych plików z 892 dodań i 132 usunięć
  1. 3 3
      toolchain/check/check.cpp
  2. 60 16
      toolchain/check/context.cpp
  3. 6 0
      toolchain/check/context.h
  4. 9 2
      toolchain/check/convert.cpp
  5. 8 2
      toolchain/check/convert.h
  6. 1 1
      toolchain/check/decl_name_stack.cpp
  7. 86 30
      toolchain/check/handle_class.cpp
  8. 3 2
      toolchain/check/handle_name.cpp
  9. 1 0
      toolchain/check/testdata/class/base.carbon
  10. 89 0
      toolchain/check/testdata/class/base_field.carbon
  11. 76 0
      toolchain/check/testdata/class/base_function_unqualified.carbon
  12. 90 0
      toolchain/check/testdata/class/base_method.carbon
  13. 134 0
      toolchain/check/testdata/class/base_method_shadow.carbon
  14. 2 0
      toolchain/check/testdata/class/derived_to_base.carbon
  15. 1 0
      toolchain/check/testdata/class/fail_abstract.carbon
  16. 187 62
      toolchain/check/testdata/class/fail_base_bad_type.carbon
  17. 87 0
      toolchain/check/testdata/class/fail_base_method_define.carbon
  18. 3 0
      toolchain/check/testdata/class/fail_base_modifiers.carbon
  19. 2 0
      toolchain/check/testdata/class/fail_base_repeated.carbon
  20. 1 0
      toolchain/check/testdata/class/fail_base_unbound.carbon
  21. 1 0
      toolchain/check/testdata/class/fail_derived_to_base.carbon
  22. 1 0
      toolchain/check/testdata/class/self_conversion.carbon
  23. 3 3
      toolchain/check/testdata/packages/fail_api_not_found.carbon
  24. 1 1
      toolchain/check/testdata/packages/fail_cycle.carbon
  25. 1 1
      toolchain/check/testdata/packages/fail_name_with_import_failure.carbon
  26. 1 0
      toolchain/diagnostics/diagnostic_kind.def
  27. 11 6
      toolchain/sem_ir/formatter.cpp
  28. 24 3
      toolchain/sem_ir/value_stores.h

+ 3 - 3
toolchain/check/check.cpp

@@ -87,7 +87,7 @@ static auto InitPackageScopeAndImports(Context& context, UnitInfo& unit_info)
   if (self_import != unit_info.package_imports_map.end()) {
     auto& package_scope =
         context.name_scopes().Get(SemIR::NameScopeId::Package);
-    package_scope.has_load_error = self_import->second.has_load_error;
+    package_scope.has_error = self_import->second.has_load_error;
 
     for (const auto& import : self_import->second.imports) {
       const auto& import_sem_ir = **import.unit_info->unit->sem_ir;
@@ -96,7 +96,7 @@ static auto InitPackageScopeAndImports(Context& context, UnitInfo& unit_info)
 
       // If an import of the current package caused an error for the imported
       // file, it transitively affects the current file too.
-      package_scope.has_load_error |= import_scope.has_load_error;
+      package_scope.has_error |= import_scope.has_error;
 
       auto ir_id = context.sem_ir().cross_ref_irs().Add(&import_sem_ir);
 
@@ -124,7 +124,7 @@ static auto InitPackageScopeAndImports(Context& context, UnitInfo& unit_info)
 
     // Push the scope.
     context.PushScope(package_inst, SemIR::NameScopeId::Package,
-                      package_scope.has_load_error);
+                      package_scope.has_error);
   } else {
     // Push the scope; there are no names to add.
     context.PushScope(package_inst, SemIR::NameScopeId::Package);

+ 60 - 16
toolchain/check/context.cpp

@@ -205,7 +205,8 @@ auto Context::ResolveIfLazyImportRef(SemIR::InstId inst_id) -> void {
   }
 }
 
-auto Context::LookupNameInDecl(Parse::NodeId parse_node, SemIR::NameId name_id,
+auto Context::LookupNameInDecl(Parse::NodeId /*parse_node*/,
+                               SemIR::NameId name_id,
                                SemIR::NameScopeId scope_id) -> SemIR::InstId {
   if (scope_id == SemIR::NameScopeId::Invalid) {
     // Look for a name in the current scope only. There are two cases where the
@@ -243,10 +244,16 @@ auto Context::LookupNameInDecl(Parse::NodeId parse_node, SemIR::NameId name_id,
     }
     return SemIR::InstId::Invalid;
   } else {
-    // TODO: Once we support `extend`, do not look into `extend`ed scopes here,
-    // following the same logic as above.
-    return LookupQualifiedName(parse_node, name_id, scope_id,
-                               /*required=*/false);
+    // We do not look into `extend`ed scopes here. A qualified name in a
+    // declaration must specify the exact scope in which the name was originally
+    // introduced:
+    //
+    //    base class A { fn F(); }
+    //    class B { extend base: A; }
+    //
+    //    // Error, no `F` in `B`.
+    //    fn B.F() {}
+    return LookupNameInExactScope(name_id, name_scopes().Get(scope_id));
   }
 }
 
@@ -296,26 +303,63 @@ auto Context::LookupUnqualifiedName(Parse::NodeId parse_node,
   return SemIR::InstId::BuiltinError;
 }
 
-auto Context::LookupQualifiedName(Parse::NodeId parse_node,
-                                  SemIR::NameId name_id,
-                                  SemIR::NameScopeId scope_id, bool required)
+auto Context::LookupNameInExactScope(SemIR::NameId name_id,
+                                     const SemIR::NameScope& scope)
     -> SemIR::InstId {
-  CARBON_CHECK(scope_id.is_valid()) << "No scope to perform lookup into";
-  const auto& scope = name_scopes().Get(scope_id);
   if (auto it = scope.names.find(name_id); it != scope.names.end()) {
     ResolveIfLazyImportRef(it->second);
     return it->second;
   }
+  return SemIR::InstId::Invalid;
+}
+
+auto Context::LookupQualifiedName(Parse::NodeId parse_node,
+                                  SemIR::NameId name_id,
+                                  SemIR::NameScopeId scope_id, bool required)
+    -> SemIR::InstId {
+  llvm::SmallVector<SemIR::NameScopeId> scope_ids = {scope_id};
+  auto result_id = SemIR::InstId::Invalid;
+  bool has_error = false;
+
+  // Walk this scope and, if nothing is found here, the scopes it extends.
+  while (!scope_ids.empty()) {
+    const auto& scope = name_scopes().Get(scope_ids.pop_back_val());
+    has_error |= scope.has_error;
+
+    auto scope_result_id = LookupNameInExactScope(name_id, scope);
+    if (!scope_result_id.is_valid()) {
+      // Nothing found in this scope: also look in its extended scopes.
+      auto extended = llvm::reverse(scope.extended_scopes);
+      scope_ids.append(extended.begin(), extended.end());
+      continue;
+    }
 
-  // TODO: Also perform lookups into `extend`ed scopes.
+    // If this is our second lookup result, diagnose an ambiguity.
+    if (result_id.is_valid()) {
+      // TODO: This is currently not reachable because the only scope that can
+      // extend is a class scope, and it can only extend a single base class.
+      // Add test coverage once this is possible.
+      CARBON_DIAGNOSTIC(
+          NameAmbiguousDueToExtend, Error,
+          "Ambiguous use of name `{0}` found in multiple extended scopes.",
+          std::string);
+      emitter_->Emit(parse_node, NameAmbiguousDueToExtend,
+                     names().GetFormatted(name_id).str());
+      // TODO: Add notes pointing to the scopes.
+      return SemIR::InstId::BuiltinError;
+    }
 
-  if (!required) {
-    return SemIR::InstId::Invalid;
+    result_id = scope_result_id;
   }
-  if (!scope.has_load_error) {
-    DiagnoseNameNotFound(parse_node, name_id);
+
+  if (required && !result_id.is_valid()) {
+    if (!has_error) {
+      DiagnoseNameNotFound(parse_node, name_id);
+    }
+    return SemIR::InstId::BuiltinError;
   }
-  return SemIR::InstId::BuiltinError;
+
+  return result_id;
 }
 
 auto Context::PushScope(SemIR::InstId scope_inst_id,

+ 6 - 0
toolchain/check/context.h

@@ -85,6 +85,12 @@ class Context {
   auto LookupUnqualifiedName(Parse::NodeId parse_node, SemIR::NameId name_id)
       -> SemIR::InstId;
 
+  // Performs a name lookup in a specified scope, returning the referenced
+  // instruction. Does not look into extended scopes. Returns an invalid
+  // instruction if the name is not found.
+  auto LookupNameInExactScope(SemIR::NameId name_id,
+                              const SemIR::NameScope& scope) -> SemIR::InstId;
+
   // Performs a qualified name lookup in a specified scope and in scopes that
   // it extends, returning the referenced instruction.
   auto LookupQualifiedName(Parse::NodeId parse_node, SemIR::NameId name_id,

+ 9 - 2
toolchain/check/convert.cpp

@@ -1032,12 +1032,19 @@ auto ConvertToValueOrRefExpr(Context& context, SemIR::InstId expr_id)
 }
 
 auto ConvertToValueOfType(Context& context, Parse::NodeId parse_node,
-                          SemIR::InstId value_id, SemIR::TypeId type_id)
+                          SemIR::InstId expr_id, SemIR::TypeId type_id)
     -> SemIR::InstId {
-  return Convert(context, parse_node, value_id,
+  return Convert(context, parse_node, expr_id,
                  {.kind = ConversionTarget::Value, .type_id = type_id});
 }
 
+auto ConvertToValueOrRefOfType(Context& context, Parse::NodeId parse_node,
+                               SemIR::InstId expr_id, SemIR::TypeId type_id)
+    -> SemIR::InstId {
+  return Convert(context, parse_node, expr_id,
+                 {.kind = ConversionTarget::ValueOrRef, .type_id = type_id});
+}
+
 auto ConvertToBoolValue(Context& context, Parse::NodeId parse_node,
                         SemIR::InstId value_id) -> SemIR::InstId {
   return ConvertToValueOfType(

+ 8 - 2
toolchain/check/convert.h

@@ -70,9 +70,15 @@ auto ConvertToValueExpr(Context& context, SemIR::InstId expr_id)
 auto ConvertToValueOrRefExpr(Context& context, SemIR::InstId expr_id)
     -> SemIR::InstId;
 
-// Converts `value_id` to a value expression of type `type_id`.
+// Converts `expr_id` to a value expression of type `type_id`.
 auto ConvertToValueOfType(Context& context, Parse::NodeId parse_node,
-                          SemIR::InstId value_id, SemIR::TypeId type_id)
+                          SemIR::InstId expr_id, SemIR::TypeId type_id)
+    -> SemIR::InstId;
+
+// Convert the given expression to a value or reference expression of the given
+// type.
+auto ConvertToValueOrRefOfType(Context& context, Parse::NodeId parse_node,
+                               SemIR::InstId expr_id, SemIR::TypeId type_id)
     -> SemIR::InstId;
 
 // Converts `value_id` to a value expression of type `bool`.

+ 1 - 1
toolchain/check/decl_name_stack.cpp

@@ -146,7 +146,7 @@ auto DeclNameStack::UpdateScopeIfNeeded(NameContext& name_context) -> void {
       name_context.state = NameContext::State::Resolved;
       name_context.target_scope_id = scope_id;
       context_->PushScope(name_context.resolved_inst_id, scope_id,
-                          context_->name_scopes().Get(scope_id).has_load_error);
+                          context_->name_scopes().Get(scope_id).has_error);
       break;
     }
     default:

+ 86 - 30
toolchain/check/handle_class.cpp

@@ -174,6 +174,77 @@ auto HandleBaseColon(Context& /*context*/, Parse::NodeId /*parse_node*/)
   return true;
 }
 
+namespace {
+// Information gathered about a base type specified in a `base` declaration.
+struct BaseInfo {
+  // A `BaseInfo` representing an erroneous base.
+  static const BaseInfo Error;
+
+  SemIR::TypeId type_id;
+  SemIR::NameScopeId scope_id;
+};
+constexpr BaseInfo BaseInfo::Error = {.type_id = SemIR::TypeId::Error,
+                                      .scope_id = SemIR::NameScopeId::Invalid};
+}  // namespace
+
+// If `type_id` is a class type, get its corresponding `SemIR::Class` object.
+// Otherwise returns `nullptr`.
+static auto TryGetAsClass(Context& context, SemIR::TypeId type_id)
+    -> SemIR::Class* {
+  auto class_type = context.types().TryGetAs<SemIR::ClassType>(type_id);
+  if (!class_type) {
+    return nullptr;
+  }
+  return &context.classes().Get(class_type->class_id);
+}
+
+// Diagnoses an attempt to derive from a final type.
+static auto DiagnoseBaseIsFinal(Context& context, Parse::NodeId parse_node,
+                                SemIR::TypeId base_type_id) -> void {
+  CARBON_DIAGNOSTIC(BaseIsFinal, Error,
+                    "Deriving from final type `{0}`. Base type must be an "
+                    "`abstract` or `base` class.",
+                    std::string);
+  context.emitter().Emit(parse_node, BaseIsFinal,
+                         context.sem_ir().StringifyType(base_type_id));
+}
+
+// Checks that the specified base type is valid.
+static auto CheckBaseType(Context& context, Parse::NodeId parse_node,
+                          SemIR::InstId base_expr_id) -> BaseInfo {
+  auto base_type_id = ExprAsType(context, parse_node, base_expr_id);
+  base_type_id = context.AsCompleteType(base_type_id, [&] {
+    CARBON_DIAGNOSTIC(IncompleteTypeInBaseDecl, Error,
+                      "Base `{0}` is an incomplete type.", std::string);
+    return context.emitter().Build(
+        parse_node, IncompleteTypeInBaseDecl,
+        context.sem_ir().StringifyType(base_type_id));
+  });
+
+  if (base_type_id == SemIR::TypeId::Error) {
+    return BaseInfo::Error;
+  }
+
+  auto* base_class_info = TryGetAsClass(context, base_type_id);
+
+  // The base must not be a final class.
+  if (!base_class_info) {
+    // For now, we treat all types that aren't introduced by a `class`
+    // declaration as being final classes.
+    // TODO: Once we have a better idea of which types are considered to be
+    // classes, produce a better diagnostic for deriving from a non-class type.
+    DiagnoseBaseIsFinal(context, parse_node, base_type_id);
+    return BaseInfo::Error;
+  }
+  if (base_class_info->inheritance_kind == SemIR::Class::Final) {
+    DiagnoseBaseIsFinal(context, parse_node, base_type_id);
+  }
+
+  CARBON_CHECK(base_class_info->scope_id.is_valid())
+      << "Complete class should have a scope";
+  return {.type_id = base_type_id, .scope_id = base_class_info->scope_id};
+}
+
 auto HandleBaseDecl(Context& context, Parse::NodeId parse_node) -> bool {
   auto base_type_expr_id = context.node_stack().PopExpr();
 
@@ -211,54 +282,39 @@ auto HandleBaseDecl(Context& context, Parse::NodeId parse_node) -> bool {
     return true;
   }
 
-  auto base_type_id = ExprAsType(context, parse_node, base_type_expr_id);
-  base_type_id = context.AsCompleteType(base_type_id, [&] {
-    CARBON_DIAGNOSTIC(IncompleteTypeInBaseDecl, Error,
-                      "Base `{0}` is an incomplete type.", std::string);
-    return context.emitter().Build(
-        parse_node, IncompleteTypeInBaseDecl,
-        context.sem_ir().StringifyType(base_type_id));
-  });
-
-  if (base_type_id != SemIR::TypeId::Error) {
-    // For now, we treat all types that aren't introduced by a `class`
-    // declaration as being final classes.
-    // TODO: Once we have a better idea of which types are considered to be
-    // classes, produce a better diagnostic for deriving from a non-class type.
-    auto base_class = context.types().TryGetAs<SemIR::ClassType>(base_type_id);
-    if (!base_class ||
-        context.classes().Get(base_class->class_id).inheritance_kind ==
-            SemIR::Class::Final) {
-      CARBON_DIAGNOSTIC(BaseIsFinal, Error,
-                        "Deriving from final type `{0}`. Base type must be an "
-                        "`abstract` or `base` class.",
-                        std::string);
-      context.emitter().Emit(parse_node, BaseIsFinal,
-                             context.sem_ir().StringifyType(base_type_id));
-    }
-  }
+  auto base_info = CheckBaseType(context, parse_node, base_type_expr_id);
 
   // The `base` value in the class scope has an unbound element type. Instance
   // binding will be performed when it's found by name lookup into an instance.
   auto field_type_inst_id = context.AddInst(SemIR::UnboundElementType{
       parse_node, context.GetBuiltinType(SemIR::BuiltinKind::TypeType),
-      class_info.self_type_id, base_type_id});
+      class_info.self_type_id, base_info.type_id});
   auto field_type_id = context.CanonicalizeType(field_type_inst_id);
   class_info.base_id = context.AddInst(SemIR::BaseDecl{
-      parse_node, field_type_id, base_type_id,
+      parse_node, field_type_id, base_info.type_id,
       SemIR::ElementIndex(
           context.args_type_info_stack().PeekCurrentBlockContents().size())});
 
   // Add a corresponding field to the object representation of the class.
   // TODO: Consider whether we want to use `partial T` here.
-  context.args_type_info_stack().AddInst(
-      SemIR::StructTypeField{parse_node, SemIR::NameId::Base, base_type_id});
+  context.args_type_info_stack().AddInst(SemIR::StructTypeField{
+      parse_node, SemIR::NameId::Base, base_info.type_id});
 
   // Bind the name `base` in the class to the base field.
   context.decl_name_stack().AddNameToLookup(
       context.decl_name_stack().MakeUnqualifiedName(parse_node,
                                                     SemIR::NameId::Base),
       class_info.base_id);
+
+  // Extend the class scope with the base class.
+  if (!!(modifiers & KeywordModifierSet::Extend)) {
+    auto& class_scope = context.name_scopes().Get(class_info.scope_id);
+    if (base_info.scope_id.is_valid()) {
+      class_scope.extended_scopes.push_back(base_info.scope_id);
+    } else {
+      class_scope.has_error = true;
+    }
+  }
   return true;
 }
 

+ 3 - 2
toolchain/check/handle_name.cpp

@@ -131,8 +131,9 @@ auto HandleMemberAccessExpr(Context& context, Parse::NodeId parse_node)
       if (auto unbound_element_type =
               context.types().TryGetAs<SemIR::UnboundElementType>(
                   member_type_id)) {
-        // TODO: Check that the unbound element type describes a member of this
-        // class. Perform a conversion of the base if necessary.
+        // Convert the base to the type of the element if necessary.
+        base_id = ConvertToValueOrRefOfType(
+            context, parse_node, base_id, unbound_element_type->class_type_id);
 
         // Find the specified element, which could be either a field or a base
         // class, and build an element access expression.

+ 1 - 0
toolchain/check/testdata/class/base.carbon

@@ -67,6 +67,7 @@ fn Access(d: Derived) -> (i32, i32) {
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .base = %.loc12_20.2
 // CHECK:STDOUT:   .d = %d
+// CHECK:STDOUT:   extend name_scope1
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Make() -> %return: Derived {

+ 89 - 0
toolchain/check/testdata/class/base_field.carbon

@@ -0,0 +1,89 @@
+// 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
+//
+// AUTOUPDATE
+
+base class Base {
+  var a: i32;
+  var b: i32;
+  var c: i32;
+}
+
+class Derived {
+  extend base: Base;
+
+  var d: i32;
+  var e: i32;
+}
+
+fn Access(p: Derived*) -> i32* {
+  return &(*p).c;
+}
+
+// CHECK:STDOUT: --- base_field.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.loc11_1.1: type = struct_type {.a: i32, .b: i32, .c: i32}
+// CHECK:STDOUT:   %.loc11_1.2: type = ptr_type {.a: i32, .b: i32, .c: i32}
+// CHECK:STDOUT:   %.loc18_1.1: type = struct_type {.base: Base, .d: i32, .e: i32}
+// CHECK:STDOUT:   %.loc18_1.2: type = struct_type {.base: {.a: i32, .b: i32, .c: i32}*, .d: i32, .e: i32}
+// CHECK:STDOUT:   %.loc18_1.3: type = ptr_type {.base: {.a: i32, .b: i32, .c: i32}*, .d: i32, .e: i32}
+// CHECK:STDOUT:   %.loc13: type = ptr_type {.base: Base, .d: i32, .e: i32}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace {.Base = %Base.decl, .Derived = %Derived.decl, .Access = %Access}
+// CHECK:STDOUT:   %Base.decl = class_decl @Base, ()
+// CHECK:STDOUT:   %Base: type = class_type @Base
+// CHECK:STDOUT:   %Derived.decl = class_decl @Derived, ()
+// CHECK:STDOUT:   %Derived: type = class_type @Derived
+// CHECK:STDOUT:   %Access: <function> = fn_decl @Access
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Base {
+// CHECK:STDOUT:   %.loc8_8.1: type = unbound_element_type Base, i32
+// CHECK:STDOUT:   %.loc8_8.2: <unbound element of class Base> = field_decl a, element0
+// CHECK:STDOUT:   %a: <unbound element of class Base> = bind_name a, %.loc8_8.2
+// CHECK:STDOUT:   %.loc9_8.1: type = unbound_element_type Base, i32
+// CHECK:STDOUT:   %.loc9_8.2: <unbound element of class Base> = field_decl b, element1
+// CHECK:STDOUT:   %b: <unbound element of class Base> = bind_name b, %.loc9_8.2
+// CHECK:STDOUT:   %.loc10_8.1: type = unbound_element_type Base, i32
+// CHECK:STDOUT:   %.loc10_8.2: <unbound element of class Base> = field_decl c, element2
+// CHECK:STDOUT:   %c: <unbound element of class Base> = bind_name c, %.loc10_8.2
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .a = %a
+// CHECK:STDOUT:   .b = %b
+// CHECK:STDOUT:   .c = %c
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Derived {
+// CHECK:STDOUT:   %Base.ref: type = name_ref Base, file.%Base
+// CHECK:STDOUT:   %.loc14_20.1: type = unbound_element_type Derived, Base
+// CHECK:STDOUT:   %.loc14_20.2: <unbound element of class Derived> = base_decl Base, element0
+// CHECK:STDOUT:   %.loc16_8.1: type = unbound_element_type Derived, i32
+// CHECK:STDOUT:   %.loc16_8.2: <unbound element of class Derived> = field_decl d, element1
+// CHECK:STDOUT:   %d: <unbound element of class Derived> = bind_name d, %.loc16_8.2
+// CHECK:STDOUT:   %.loc17_8.1: type = unbound_element_type Derived, i32
+// CHECK:STDOUT:   %.loc17_8.2: <unbound element of class Derived> = field_decl e, element2
+// CHECK:STDOUT:   %e: <unbound element of class Derived> = bind_name e, %.loc17_8.2
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .base = %.loc14_20.2
+// CHECK:STDOUT:   .d = %d
+// CHECK:STDOUT:   .e = %e
+// CHECK:STDOUT:   extend name_scope1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Access(%p: Derived*) -> i32* {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %p.ref: Derived* = name_ref p, %p
+// CHECK:STDOUT:   %.loc21_12: ref Derived = deref %p.ref
+// CHECK:STDOUT:   %.loc21_15.1: ref Base = class_element_access %.loc21_12, element0
+// CHECK:STDOUT:   %.loc21_15.2: ref Base = converted %.loc21_12, %.loc21_15.1
+// CHECK:STDOUT:   %.loc21_15.3: ref i32 = class_element_access %.loc21_15.2, element2
+// CHECK:STDOUT:   %.loc21_10: i32* = address_of %.loc21_15.3
+// CHECK:STDOUT:   return %.loc21_10
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 76 - 0
toolchain/check/testdata/class/base_function_unqualified.carbon

@@ -0,0 +1,76 @@
+// 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
+//
+// AUTOUPDATE
+
+base class Base {
+  fn F();
+}
+
+class Derived {
+  extend base: Base;
+
+  fn G() { F(); }
+  fn H();
+}
+
+fn Derived.H() {
+  F();
+}
+
+// CHECK:STDOUT: --- base_function_unqualified.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.loc9_1.1: type = struct_type {}
+// CHECK:STDOUT:   %.loc9_1.2: type = tuple_type ()
+// CHECK:STDOUT:   %.loc7: type = ptr_type {}
+// CHECK:STDOUT:   %.loc16: type = struct_type {.base: Base}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace {.Base = %Base.decl, .Derived = %Derived.decl}
+// CHECK:STDOUT:   %Base.decl = class_decl @Base, ()
+// CHECK:STDOUT:   %Base: type = class_type @Base
+// CHECK:STDOUT:   %Derived.decl = class_decl @Derived, ()
+// CHECK:STDOUT:   %Derived: type = class_type @Derived
+// CHECK:STDOUT:   %H: <function> = fn_decl @H
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Base {
+// CHECK:STDOUT:   %F: <function> = fn_decl @F
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .F = %F
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Derived {
+// CHECK:STDOUT:   %Base.ref: type = name_ref Base, file.%Base
+// CHECK:STDOUT:   %.loc12_20.1: type = unbound_element_type Derived, Base
+// CHECK:STDOUT:   %.loc12_20.2: <unbound element of class Derived> = base_decl Base, element0
+// CHECK:STDOUT:   %G: <function> = fn_decl @G
+// CHECK:STDOUT:   %H: <function> = fn_decl @H
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .base = %.loc12_20.2
+// CHECK:STDOUT:   .G = %G
+// CHECK:STDOUT:   .H = %H
+// CHECK:STDOUT:   extend name_scope1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F();
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @G() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %F.ref: <function> = name_ref F, @Base.%F
+// CHECK:STDOUT:   %.loc14: init () = call %F.ref()
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @H() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %F.ref: <function> = name_ref F, @Base.%F
+// CHECK:STDOUT:   %.loc19: init () = call %F.ref()
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 90 - 0
toolchain/check/testdata/class/base_method.carbon

@@ -0,0 +1,90 @@
+// 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
+//
+// AUTOUPDATE
+
+base class Base {
+  var a: i32;
+
+  fn F[addr self: Self*]();
+}
+
+fn Base.F[addr self: Base*]() {
+  (*self).a = 1;
+}
+
+class Derived {
+  extend base: Base;
+}
+
+fn Call(p: Derived*) {
+  (*p).F();
+}
+
+// CHECK:STDOUT: --- base_method.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.loc11: type = struct_type {.a: i32}
+// CHECK:STDOUT:   %.loc7: type = ptr_type {.a: i32}
+// CHECK:STDOUT:   %.loc19_1.1: type = struct_type {.base: Base}
+// CHECK:STDOUT:   %.loc19_1.2: type = struct_type {.base: {.a: i32}*}
+// CHECK:STDOUT:   %.loc17: type = ptr_type {.base: Base}
+// CHECK:STDOUT:   %.loc22: type = tuple_type ()
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace {.Base = %Base.decl, .Derived = %Derived.decl, .Call = %Call}
+// CHECK:STDOUT:   %Base.decl = class_decl @Base, ()
+// CHECK:STDOUT:   %Base: type = class_type @Base
+// CHECK:STDOUT:   %F: <function> = fn_decl @F
+// CHECK:STDOUT:   %Derived.decl = class_decl @Derived, ()
+// CHECK:STDOUT:   %Derived: type = class_type @Derived
+// CHECK:STDOUT:   %Call: <function> = fn_decl @Call
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Base {
+// CHECK:STDOUT:   %.loc8_8.1: type = unbound_element_type Base, i32
+// CHECK:STDOUT:   %.loc8_8.2: <unbound element of class Base> = field_decl a, element0
+// CHECK:STDOUT:   %a: <unbound element of class Base> = bind_name a, %.loc8_8.2
+// CHECK:STDOUT:   %F: <function> = fn_decl @F
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .a = %a
+// CHECK:STDOUT:   .F = %F
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Derived {
+// CHECK:STDOUT:   %Base.ref: type = name_ref Base, file.%Base
+// CHECK:STDOUT:   %.loc18_20.1: type = unbound_element_type Derived, Base
+// CHECK:STDOUT:   %.loc18_20.2: <unbound element of class Derived> = base_decl Base, element0
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .base = %.loc18_20.2
+// CHECK:STDOUT:   extend name_scope1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F[%self.addr: Base*]() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %self.ref: Base* = name_ref self, %self.addr
+// CHECK:STDOUT:   %.loc14_4: ref Base = deref %self.ref
+// CHECK:STDOUT:   %.loc14_10: ref i32 = class_element_access %.loc14_4, element0
+// CHECK:STDOUT:   %.loc14_15: i32 = int_literal 1
+// CHECK:STDOUT:   assign %.loc14_10, %.loc14_15
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Call(%p: Derived*) {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %p.ref: Derived* = name_ref p, %p
+// CHECK:STDOUT:   %.loc22_4.1: ref Derived = deref %p.ref
+// CHECK:STDOUT:   %.loc22_7: <bound method> = bound_method %.loc22_4.1, @Base.%F
+// CHECK:STDOUT:   %.loc22_4.2: Derived* = address_of %.loc22_4.1
+// CHECK:STDOUT:   %.loc22_9.1: ref Derived = deref %.loc22_4.2
+// CHECK:STDOUT:   %.loc22_9.2: ref Base = class_element_access %.loc22_9.1, element0
+// CHECK:STDOUT:   %.loc22_9.3: Base* = address_of %.loc22_9.2
+// CHECK:STDOUT:   %.loc22_9.4: Base* = converted %.loc22_4.2, %.loc22_9.3
+// CHECK:STDOUT:   %.loc22_9.5: init () = call %.loc22_7(%.loc22_9.4)
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 134 - 0
toolchain/check/testdata/class/base_method_shadow.carbon

@@ -0,0 +1,134 @@
+// 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
+//
+// AUTOUPDATE
+
+base class A {
+  fn F[addr self: Self*]();
+}
+
+base class B {
+  extend base: A;
+  fn F[addr self: Self*]();
+}
+
+class C {
+  extend base: B;
+  fn F[addr self: Self*]();
+}
+
+class D {
+  extend base: B;
+}
+
+fn Call(a: A*, b: B*, c: C*, d: D*) {
+  (*a).F();
+  (*b).F();
+  (*c).F();
+  (*d).F();
+}
+
+// CHECK:STDOUT: --- base_method_shadow.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.loc9_1.1: type = struct_type {}
+// CHECK:STDOUT:   %.loc9_1.2: type = tuple_type ()
+// CHECK:STDOUT:   %.loc7: type = ptr_type {}
+// CHECK:STDOUT:   %.loc14_1.1: type = struct_type {.base: A}
+// CHECK:STDOUT:   %.loc14_1.2: type = struct_type {.base: {}*}
+// CHECK:STDOUT:   %.loc11: type = ptr_type {.base: A}
+// CHECK:STDOUT:   %.loc19_1.1: type = struct_type {.base: B}
+// CHECK:STDOUT:   %.loc19_1.2: type = struct_type {.base: {.base: A}*}
+// CHECK:STDOUT:   %.loc16: type = ptr_type {.base: B}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace {.A = %A.decl, .B = %B.decl, .C = %C.decl, .D = %D.decl, .Call = %Call}
+// CHECK:STDOUT:   %A.decl = class_decl @A, ()
+// CHECK:STDOUT:   %A: type = class_type @A
+// CHECK:STDOUT:   %B.decl = class_decl @B, ()
+// CHECK:STDOUT:   %B: type = class_type @B
+// CHECK:STDOUT:   %C.decl = class_decl @C, ()
+// CHECK:STDOUT:   %C: type = class_type @C
+// CHECK:STDOUT:   %D.decl = class_decl @D, ()
+// CHECK:STDOUT:   %D: type = class_type @D
+// CHECK:STDOUT:   %Call: <function> = fn_decl @Call
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @A {
+// CHECK:STDOUT:   %F: <function> = fn_decl @F.1
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .F = %F
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @B {
+// CHECK:STDOUT:   %A.ref: type = name_ref A, file.%A
+// CHECK:STDOUT:   %.loc12_17.1: type = unbound_element_type B, A
+// CHECK:STDOUT:   %.loc12_17.2: <unbound element of class B> = base_decl A, element0
+// CHECK:STDOUT:   %F: <function> = fn_decl @F.2
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .base = %.loc12_17.2
+// CHECK:STDOUT:   .F = %F
+// CHECK:STDOUT:   extend name_scope1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
+// CHECK:STDOUT:   %B.ref: type = name_ref B, file.%B
+// CHECK:STDOUT:   %.loc17_17.1: type = unbound_element_type C, B
+// CHECK:STDOUT:   %.loc17_17.2: <unbound element of class C> = base_decl B, element0
+// CHECK:STDOUT:   %F: <function> = fn_decl @F.3
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .base = %.loc17_17.2
+// CHECK:STDOUT:   .F = %F
+// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @D {
+// CHECK:STDOUT:   %B.ref: type = name_ref B, file.%B
+// CHECK:STDOUT:   %.loc22_17.1: type = unbound_element_type D, B
+// CHECK:STDOUT:   %.loc22_17.2: <unbound element of class D> = base_decl B, element0
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .base = %.loc22_17.2
+// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F.1[%self.addr: A*]();
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F.2[%self.addr: B*]();
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F.3[%self.addr: C*]();
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Call(%a: A*, %b: B*, %c: C*, %d: D*) {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %a.ref: A* = name_ref a, %a
+// CHECK:STDOUT:   %.loc26_4.1: ref A = deref %a.ref
+// CHECK:STDOUT:   %.loc26_7: <bound method> = bound_method %.loc26_4.1, @A.%F
+// CHECK:STDOUT:   %.loc26_4.2: A* = address_of %.loc26_4.1
+// CHECK:STDOUT:   %.loc26_9: init () = call %.loc26_7(%.loc26_4.2)
+// CHECK:STDOUT:   %b.ref: B* = name_ref b, %b
+// CHECK:STDOUT:   %.loc27_4.1: ref B = deref %b.ref
+// CHECK:STDOUT:   %.loc27_7: <bound method> = bound_method %.loc27_4.1, @B.%F
+// CHECK:STDOUT:   %.loc27_4.2: B* = address_of %.loc27_4.1
+// CHECK:STDOUT:   %.loc27_9: init () = call %.loc27_7(%.loc27_4.2)
+// CHECK:STDOUT:   %c.ref: C* = name_ref c, %c
+// CHECK:STDOUT:   %.loc28_4.1: ref C = deref %c.ref
+// CHECK:STDOUT:   %.loc28_7: <bound method> = bound_method %.loc28_4.1, @C.%F
+// CHECK:STDOUT:   %.loc28_4.2: C* = address_of %.loc28_4.1
+// CHECK:STDOUT:   %.loc28_9: init () = call %.loc28_7(%.loc28_4.2)
+// CHECK:STDOUT:   %d.ref: D* = name_ref d, %d
+// CHECK:STDOUT:   %.loc29_4.1: ref D = deref %d.ref
+// CHECK:STDOUT:   %.loc29_7: <bound method> = bound_method %.loc29_4.1, @B.%F
+// CHECK:STDOUT:   %.loc29_4.2: D* = address_of %.loc29_4.1
+// CHECK:STDOUT:   %.loc29_9.1: ref D = deref %.loc29_4.2
+// CHECK:STDOUT:   %.loc29_9.2: ref B = class_element_access %.loc29_9.1, element0
+// CHECK:STDOUT:   %.loc29_9.3: B* = address_of %.loc29_9.2
+// CHECK:STDOUT:   %.loc29_9.4: B* = converted %.loc29_4.2, %.loc29_9.3
+// CHECK:STDOUT:   %.loc29_9.5: init () = call %.loc29_7(%.loc29_9.4)
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 2 - 0
toolchain/check/testdata/class/derived_to_base.carbon

@@ -87,6 +87,7 @@ fn ConvertInit() {
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .base = %.loc12_17.2
 // CHECK:STDOUT:   .b = %b
+// CHECK:STDOUT:   extend name_scope1
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @C {
@@ -100,6 +101,7 @@ fn ConvertInit() {
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .base = %.loc17_17.2
 // CHECK:STDOUT:   .c = %c
+// CHECK:STDOUT:   extend name_scope2
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @ConvertCToB(%p: C*) -> B* {

+ 1 - 0
toolchain/check/testdata/class/fail_abstract.carbon

@@ -71,6 +71,7 @@ fn Access(d: Derived) -> (i32, i32) {
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .base = %.loc12_24.2
 // CHECK:STDOUT:   .d = %d
+// CHECK:STDOUT:   extend name_scope1
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Make() -> %return: Derived {

+ 187 - 62
toolchain/check/testdata/class/fail_base_bad_type.carbon

@@ -5,7 +5,19 @@
 // AUTOUPDATE
 
 base class Base {}
-class Final {}
+class Final {
+  var a: i32;
+}
+
+class DeriveFromError {
+  // CHECK:STDERR: fail_base_bad_type.carbon:[[@LINE+3]]:16: ERROR: Name `error` not found.
+  // CHECK:STDERR:   extend base: error;
+  // CHECK:STDERR:                ^~~~~
+  extend base: error;
+}
+
+// This should not produce an error.
+fn AccessMemberWithInvalidBaseError(p: DeriveFromError*) -> i32 { return (*p).n; }
 
 class DeriveFromNonType {
   // CHECK:STDERR: fail_base_bad_type.carbon:[[@LINE+3]]:3: ERROR: Cannot implicitly convert from `i32` to `type`.
@@ -14,6 +26,8 @@ class DeriveFromNonType {
   extend base: 32;
 }
 
+fn AccessMemberWithInvalidBasNonType(p: DeriveFromNonType*) -> i32 { return (*p).n; }
+
 class DeriveFromi32 {
   // CHECK:STDERR: fail_base_bad_type.carbon:[[@LINE+3]]:3: ERROR: Deriving from final type `i32`. Base type must be an `abstract` or `base` class.
   // CHECK:STDERR:   extend base: i32;
@@ -23,8 +37,13 @@ class DeriveFromi32 {
 
 // It's not really important whether this conversion produces an error or not,
 // but it shouldn't crash.
+// CHECK:STDERR: fail_base_bad_type.carbon:[[@LINE+3]]:53: ERROR: Cannot implicitly convert from `DeriveFromi32*` to `i32*`.
+// CHECK:STDERR: fn ConvertToBadBasei32(p: DeriveFromi32*) -> i32* { return p; }
+// CHECK:STDERR:                                                     ^~~~~~~~~
 fn ConvertToBadBasei32(p: DeriveFromi32*) -> i32* { return p; }
 
+fn AccessMemberWithInvalidBasei32(p: DeriveFromi32*) -> i32 { return (*p).n; }
+
 class DeriveFromTuple {
   // CHECK:STDERR: fail_base_bad_type.carbon:[[@LINE+3]]:3: ERROR: Deriving from final type `(Base,)`. Base type must be an `abstract` or `base` class.
   // CHECK:STDERR:   extend base: (Base,);
@@ -32,8 +51,13 @@ class DeriveFromTuple {
   extend base: (Base,);
 }
 
+// CHECK:STDERR: fail_base_bad_type.carbon:[[@LINE+3]]:61: ERROR: Cannot implicitly convert from `DeriveFromTuple*` to `(Base,)*`.
+// CHECK:STDERR: fn ConvertToBadBaseTuple(p: DeriveFromTuple*) -> (Base,)* { return p; }
+// CHECK:STDERR:                                                             ^~~~~~~~~
 fn ConvertToBadBaseTuple(p: DeriveFromTuple*) -> (Base,)* { return p; }
 
+fn AccessMemberWithInvalidBaseTuple(p: DeriveFromTuple*) -> i32 { return (*p).n; }
+
 // TODO: Should we allow this?
 // We do allow `{.base = {.a: i32, .b: i32}}`.
 class DeriveFromStruct {
@@ -43,8 +67,14 @@ class DeriveFromStruct {
   extend base: {.a: i32, .b: i32};
 }
 
+// CHECK:STDERR: fail_base_bad_type.carbon:[[@LINE+3]]:74: ERROR: Cannot implicitly convert from `DeriveFromStruct*` to `{.a: i32, .b: i32}*`.
+// CHECK:STDERR: fn ConvertToBadBaseStruct(p: DeriveFromStruct*) -> {.a: i32, .b: i32}* { return p; }
+// CHECK:STDERR:                                                                          ^~~~~~~~~
 fn ConvertToBadBaseStruct(p: DeriveFromStruct*) -> {.a: i32, .b: i32}* { return p; }
 
+// It would be OK to reject this if we start actually looking in the struct type.
+fn AccessMemberWithInvalidBaseStruct(p: DeriveFromStruct*) -> i32 { return (*p).n; }
+
 base class Incomplete;
 
 class DeriveFromIncomplete {
@@ -62,6 +92,8 @@ class DeriveFromIncomplete {
 // CHECK:STDERR:                                                                          ^~~~~~~~~
 fn ConvertToBadBaseIncomplete(p: DeriveFromIncomplete*) -> Incomplete* { return p; }
 
+fn AccessMemberWithInvalidBaseIncomplete(p: DeriveFromIncomplete*) -> i32 { return (*p).n; }
+
 class DeriveFromFinal {
   // CHECK:STDERR: fail_base_bad_type.carbon:[[@LINE+3]]:3: ERROR: Deriving from final type `Final`. Base type must be an `abstract` or `base` class.
   // CHECK:STDERR:   extend base: Final;
@@ -69,58 +101,76 @@ class DeriveFromFinal {
   extend base: Final;
 }
 
-fn ConvertToBadBaseFinal(p: DeriveFromFinal*) -> Final* { return p; }
+// For error recovery purposes, we derive from the final type anyway.
+fn ConvertToBadBaseFinal(p: DeriveFromFinal*) -> Final* {
+  return p;
+}
+
+fn AccessMemberWithInvalidBaseFinal_WithMember(p: DeriveFromFinal*) -> i32 {
+  return (*p).a;
+}
+
+fn AccessMemberWithInvalidBaseFinal_NoMember(p: DeriveFromFinal*) -> i32 {
+  // CHECK:STDERR: fail_base_bad_type.carbon:[[@LINE+3]]:10: ERROR: Name `b` not found.
+  // CHECK:STDERR:   return (*p).b;
+  // CHECK:STDERR:          ^~~~~~
+  return (*p).b;
+}
 
 // CHECK:STDOUT: --- fail_base_bad_type.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %.loc7_18.1: type = struct_type {}
-// CHECK:STDOUT:   %.loc15: type = struct_type {.base: <error>}
-// CHECK:STDOUT:   %.loc22: type = struct_type {.base: i32}
-// CHECK:STDOUT:   %.loc17: type = ptr_type {.base: i32}
-// CHECK:STDOUT:   %.loc32_22: type = tuple_type (type)
-// CHECK:STDOUT:   %.loc32_23.1: type = tuple_type (Base)
+// CHECK:STDOUT:   %.loc10: type = struct_type {.a: i32}
+// CHECK:STDOUT:   %.loc17: type = struct_type {.base: <error>}
+// CHECK:STDOUT:   %.loc12: type = ptr_type {.base: <error>}
+// CHECK:STDOUT:   %.loc51_22: type = tuple_type (type)
+// CHECK:STDOUT:   %.loc51_23.1: type = tuple_type (Base)
 // CHECK:STDOUT:   %.loc7_18.2: type = tuple_type ()
 // CHECK:STDOUT:   %.loc7_17: type = ptr_type {}
-// CHECK:STDOUT:   %.loc32_23.2: type = tuple_type ({}*)
-// CHECK:STDOUT:   %.loc33_1.1: type = struct_type {.base: (Base,)}
-// CHECK:STDOUT:   %.loc33_1.2: type = struct_type {.base: ({}*,)}
-// CHECK:STDOUT:   %.loc28: type = ptr_type {.base: (Base,)}
-// CHECK:STDOUT:   %.loc43: type = ptr_type {.a: i32, .b: i32}
-// CHECK:STDOUT:   %.loc44_1.1: type = struct_type {.base: {.a: i32, .b: i32}}
-// CHECK:STDOUT:   %.loc44_1.2: type = struct_type {.base: {.a: i32, .b: i32}*}
-// CHECK:STDOUT:   %.loc39: type = ptr_type {.base: {.a: i32, .b: i32}}
-// CHECK:STDOUT:   %.loc50: type = ptr_type {.base: <error>}
-// CHECK:STDOUT:   %.loc70_1.1: type = struct_type {.base: Final}
-// CHECK:STDOUT:   %.loc70_1.2: type = struct_type {.base: {}*}
-// CHECK:STDOUT:   %.loc65: type = ptr_type {.base: Final}
+// CHECK:STDOUT:   %.loc51_23.2: type = tuple_type ({}*)
+// CHECK:STDOUT:   %.loc67: type = ptr_type {.a: i32, .b: i32}
+// CHECK:STDOUT:   %.loc8: type = ptr_type {.a: i32}
+// CHECK:STDOUT:   %.loc102_1.1: type = struct_type {.base: Final}
+// CHECK:STDOUT:   %.loc102_1.2: type = struct_type {.base: {.a: i32}*}
+// CHECK:STDOUT:   %.loc97: type = ptr_type {.base: Final}
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
-// CHECK:STDOUT:   package: <namespace> = namespace {.Base = %Base.decl, .Final = %Final.decl, .DeriveFromNonType = %DeriveFromNonType.decl, .DeriveFromi32 = %DeriveFromi32.decl, .ConvertToBadBasei32 = %ConvertToBadBasei32, .DeriveFromTuple = %DeriveFromTuple.decl, .ConvertToBadBaseTuple = %ConvertToBadBaseTuple, .DeriveFromStruct = %DeriveFromStruct.decl, .ConvertToBadBaseStruct = %ConvertToBadBaseStruct, .Incomplete = %Incomplete.decl, .DeriveFromIncomplete = %DeriveFromIncomplete.decl, .ConvertToBadBaseIncomplete = %ConvertToBadBaseIncomplete, .DeriveFromFinal = %DeriveFromFinal.decl, .ConvertToBadBaseFinal = %ConvertToBadBaseFinal}
+// CHECK:STDOUT:   package: <namespace> = namespace {.Base = %Base.decl, .Final = %Final.decl, .DeriveFromError = %DeriveFromError.decl, .AccessMemberWithInvalidBaseError = %AccessMemberWithInvalidBaseError, .DeriveFromNonType = %DeriveFromNonType.decl, .AccessMemberWithInvalidBasNonType = %AccessMemberWithInvalidBasNonType, .DeriveFromi32 = %DeriveFromi32.decl, .ConvertToBadBasei32 = %ConvertToBadBasei32, .AccessMemberWithInvalidBasei32 = %AccessMemberWithInvalidBasei32, .DeriveFromTuple = %DeriveFromTuple.decl, .ConvertToBadBaseTuple = %ConvertToBadBaseTuple, .AccessMemberWithInvalidBaseTuple = %AccessMemberWithInvalidBaseTuple, .DeriveFromStruct = %DeriveFromStruct.decl, .ConvertToBadBaseStruct = %ConvertToBadBaseStruct, .AccessMemberWithInvalidBaseStruct = %AccessMemberWithInvalidBaseStruct, .Incomplete = %Incomplete.decl, .DeriveFromIncomplete = %DeriveFromIncomplete.decl, .ConvertToBadBaseIncomplete = %ConvertToBadBaseIncomplete, .AccessMemberWithInvalidBaseIncomplete = %AccessMemberWithInvalidBaseIncomplete, .DeriveFromFinal = %DeriveFromFinal.decl, .ConvertToBadBaseFinal = %ConvertToBadBaseFinal, .AccessMemberWithInvalidBaseFinal_WithMember = %AccessMemberWithInvalidBaseFinal_WithMember, .AccessMemberWithInvalidBaseFinal_NoMember = %AccessMemberWithInvalidBaseFinal_NoMember}
 // CHECK:STDOUT:   %Base.decl = class_decl @Base, ()
 // CHECK:STDOUT:   %Base: type = class_type @Base
 // CHECK:STDOUT:   %Final.decl = class_decl @Final, ()
 // CHECK:STDOUT:   %Final: type = class_type @Final
+// CHECK:STDOUT:   %DeriveFromError.decl = class_decl @DeriveFromError, ()
+// CHECK:STDOUT:   %DeriveFromError: type = class_type @DeriveFromError
+// CHECK:STDOUT:   %AccessMemberWithInvalidBaseError: <function> = fn_decl @AccessMemberWithInvalidBaseError
 // CHECK:STDOUT:   %DeriveFromNonType.decl = class_decl @DeriveFromNonType, ()
 // CHECK:STDOUT:   %DeriveFromNonType: type = class_type @DeriveFromNonType
+// CHECK:STDOUT:   %AccessMemberWithInvalidBasNonType: <function> = fn_decl @AccessMemberWithInvalidBasNonType
 // CHECK:STDOUT:   %DeriveFromi32.decl = class_decl @DeriveFromi32, ()
 // CHECK:STDOUT:   %DeriveFromi32: type = class_type @DeriveFromi32
 // CHECK:STDOUT:   %ConvertToBadBasei32: <function> = fn_decl @ConvertToBadBasei32
+// CHECK:STDOUT:   %AccessMemberWithInvalidBasei32: <function> = fn_decl @AccessMemberWithInvalidBasei32
 // CHECK:STDOUT:   %DeriveFromTuple.decl = class_decl @DeriveFromTuple, ()
 // CHECK:STDOUT:   %DeriveFromTuple: type = class_type @DeriveFromTuple
 // CHECK:STDOUT:   %ConvertToBadBaseTuple: <function> = fn_decl @ConvertToBadBaseTuple
+// CHECK:STDOUT:   %AccessMemberWithInvalidBaseTuple: <function> = fn_decl @AccessMemberWithInvalidBaseTuple
 // CHECK:STDOUT:   %DeriveFromStruct.decl = class_decl @DeriveFromStruct, ()
 // CHECK:STDOUT:   %DeriveFromStruct: type = class_type @DeriveFromStruct
 // CHECK:STDOUT:   %ConvertToBadBaseStruct: <function> = fn_decl @ConvertToBadBaseStruct
+// CHECK:STDOUT:   %AccessMemberWithInvalidBaseStruct: <function> = fn_decl @AccessMemberWithInvalidBaseStruct
 // CHECK:STDOUT:   %Incomplete.decl = class_decl @Incomplete, ()
 // CHECK:STDOUT:   %Incomplete: type = class_type @Incomplete
 // CHECK:STDOUT:   %DeriveFromIncomplete.decl = class_decl @DeriveFromIncomplete, ()
 // CHECK:STDOUT:   %DeriveFromIncomplete: type = class_type @DeriveFromIncomplete
 // CHECK:STDOUT:   %ConvertToBadBaseIncomplete: <function> = fn_decl @ConvertToBadBaseIncomplete
+// CHECK:STDOUT:   %AccessMemberWithInvalidBaseIncomplete: <function> = fn_decl @AccessMemberWithInvalidBaseIncomplete
 // CHECK:STDOUT:   %DeriveFromFinal.decl = class_decl @DeriveFromFinal, ()
 // CHECK:STDOUT:   %DeriveFromFinal: type = class_type @DeriveFromFinal
 // CHECK:STDOUT:   %ConvertToBadBaseFinal: <function> = fn_decl @ConvertToBadBaseFinal
+// CHECK:STDOUT:   %AccessMemberWithInvalidBaseFinal_WithMember: <function> = fn_decl @AccessMemberWithInvalidBaseFinal_WithMember
+// CHECK:STDOUT:   %AccessMemberWithInvalidBaseFinal_NoMember: <function> = fn_decl @AccessMemberWithInvalidBaseFinal_NoMember
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @Base {
@@ -129,95 +179,143 @@ fn ConvertToBadBaseFinal(p: DeriveFromFinal*) -> Final* { return p; }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @Final {
+// CHECK:STDOUT:   %.loc9_8.1: type = unbound_element_type Final, i32
+// CHECK:STDOUT:   %.loc9_8.2: <unbound element of class Final> = field_decl a, element0
+// CHECK:STDOUT:   %a: <unbound element of class Final> = bind_name a, %.loc9_8.2
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .a = %a
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @DeriveFromError {
+// CHECK:STDOUT:   %error.ref: <error> = name_ref error, <error>
+// CHECK:STDOUT:   %.loc16_21.1: type = unbound_element_type DeriveFromError, <error>
+// CHECK:STDOUT:   %.loc16_21.2: <unbound element of class DeriveFromError> = base_decl <error>, element0
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .base = %.loc16_21.2
+// CHECK:STDOUT:   has_error
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @DeriveFromNonType {
-// CHECK:STDOUT:   %.loc14_16: i32 = int_literal 32
-// CHECK:STDOUT:   %.loc14_18.1: type = unbound_element_type DeriveFromNonType, <error>
-// CHECK:STDOUT:   %.loc14_18.2: <unbound element of class DeriveFromNonType> = base_decl <error>, element0
+// CHECK:STDOUT:   %.loc26_16: i32 = int_literal 32
+// CHECK:STDOUT:   %.loc26_18.1: type = unbound_element_type DeriveFromNonType, <error>
+// CHECK:STDOUT:   %.loc26_18.2: <unbound element of class DeriveFromNonType> = base_decl <error>, element0
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .base = %.loc14_18.2
+// CHECK:STDOUT:   .base = %.loc26_18.2
+// CHECK:STDOUT:   has_error
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @DeriveFromi32 {
-// CHECK:STDOUT:   %.loc21_19.1: type = unbound_element_type DeriveFromi32, i32
-// CHECK:STDOUT:   %.loc21_19.2: <unbound element of class DeriveFromi32> = base_decl i32, element0
+// CHECK:STDOUT:   %.loc35_19.1: type = unbound_element_type DeriveFromi32, <error>
+// CHECK:STDOUT:   %.loc35_19.2: <unbound element of class DeriveFromi32> = base_decl <error>, element0
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .base = %.loc21_19.2
+// CHECK:STDOUT:   .base = %.loc35_19.2
+// CHECK:STDOUT:   has_error
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @DeriveFromTuple {
 // CHECK:STDOUT:   %Base.ref: type = name_ref Base, file.%Base
-// CHECK:STDOUT:   %.loc32_22: (type,) = tuple_literal (%Base.ref)
-// CHECK:STDOUT:   %.loc32_23.1: type = converted %.loc32_22, constants.%.loc32_23.1
-// CHECK:STDOUT:   %.loc32_23.2: type = unbound_element_type DeriveFromTuple, (Base,)
-// CHECK:STDOUT:   %.loc32_23.3: <unbound element of class DeriveFromTuple> = base_decl (Base,), element0
+// CHECK:STDOUT:   %.loc51_22: (type,) = tuple_literal (%Base.ref)
+// CHECK:STDOUT:   %.loc51_23.1: type = converted %.loc51_22, constants.%.loc51_23.1
+// CHECK:STDOUT:   %.loc51_23.2: type = unbound_element_type DeriveFromTuple, <error>
+// CHECK:STDOUT:   %.loc51_23.3: <unbound element of class DeriveFromTuple> = base_decl <error>, element0
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .base = %.loc32_23.3
+// CHECK:STDOUT:   .base = %.loc51_23.3
+// CHECK:STDOUT:   has_error
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @DeriveFromStruct {
-// CHECK:STDOUT:   %.loc43_33: type = struct_type {.a: i32, .b: i32}
-// CHECK:STDOUT:   %.loc43_34.1: type = unbound_element_type DeriveFromStruct, {.a: i32, .b: i32}
-// CHECK:STDOUT:   %.loc43_34.2: <unbound element of class DeriveFromStruct> = base_decl {.a: i32, .b: i32}, element0
+// CHECK:STDOUT:   %.loc67_33: type = struct_type {.a: i32, .b: i32}
+// CHECK:STDOUT:   %.loc67_34.1: type = unbound_element_type DeriveFromStruct, <error>
+// CHECK:STDOUT:   %.loc67_34.2: <unbound element of class DeriveFromStruct> = base_decl <error>, element0
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .base = %.loc43_34.2
+// CHECK:STDOUT:   .base = %.loc67_34.2
+// CHECK:STDOUT:   has_error
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @Incomplete;
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @DeriveFromIncomplete {
 // CHECK:STDOUT:   %Incomplete.ref: type = name_ref Incomplete, file.%Incomplete
-// CHECK:STDOUT:   %.loc57_26.1: type = unbound_element_type DeriveFromIncomplete, <error>
-// CHECK:STDOUT:   %.loc57_26.2: <unbound element of class DeriveFromIncomplete> = base_decl <error>, element0
+// CHECK:STDOUT:   %.loc87_26.1: type = unbound_element_type DeriveFromIncomplete, <error>
+// CHECK:STDOUT:   %.loc87_26.2: <unbound element of class DeriveFromIncomplete> = base_decl <error>, element0
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .base = %.loc57_26.2
+// CHECK:STDOUT:   .base = %.loc87_26.2
+// CHECK:STDOUT:   has_error
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @DeriveFromFinal {
 // CHECK:STDOUT:   %Final.ref: type = name_ref Final, file.%Final
-// CHECK:STDOUT:   %.loc69_21.1: type = unbound_element_type DeriveFromFinal, Final
-// CHECK:STDOUT:   %.loc69_21.2: <unbound element of class DeriveFromFinal> = base_decl Final, element0
+// CHECK:STDOUT:   %.loc101_21.1: type = unbound_element_type DeriveFromFinal, Final
+// CHECK:STDOUT:   %.loc101_21.2: <unbound element of class DeriveFromFinal> = base_decl Final, element0
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .base = %.loc69_21.2
+// CHECK:STDOUT:   .base = %.loc101_21.2
+// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @AccessMemberWithInvalidBaseError(%p: DeriveFromError*) -> i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %p.ref: DeriveFromError* = name_ref p, %p
+// CHECK:STDOUT:   %.loc20: ref DeriveFromError = deref %p.ref
+// CHECK:STDOUT:   %n.ref: <error> = name_ref n, <error>
+// CHECK:STDOUT:   return <error>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @AccessMemberWithInvalidBasNonType(%p: DeriveFromNonType*) -> i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %p.ref: DeriveFromNonType* = name_ref p, %p
+// CHECK:STDOUT:   %.loc29: ref DeriveFromNonType = deref %p.ref
+// CHECK:STDOUT:   %n.ref: <error> = name_ref n, <error>
+// CHECK:STDOUT:   return <error>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @ConvertToBadBasei32(%p: DeriveFromi32*) -> i32* {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %p.ref: DeriveFromi32* = name_ref p, %p
-// CHECK:STDOUT:   %.loc26_61.1: ref DeriveFromi32 = deref %p.ref
-// CHECK:STDOUT:   %.loc26_61.2: ref i32 = class_element_access %.loc26_61.1, element0
-// CHECK:STDOUT:   %.loc26_61.3: i32* = address_of %.loc26_61.2
-// CHECK:STDOUT:   %.loc26_61.4: i32* = converted %p.ref, %.loc26_61.3
-// CHECK:STDOUT:   return %.loc26_61.4
+// CHECK:STDOUT:   return <error>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @AccessMemberWithInvalidBasei32(%p: DeriveFromi32*) -> i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %p.ref: DeriveFromi32* = name_ref p, %p
+// CHECK:STDOUT:   %.loc45: ref DeriveFromi32 = deref %p.ref
+// CHECK:STDOUT:   %n.ref: <error> = name_ref n, <error>
+// CHECK:STDOUT:   return <error>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @ConvertToBadBaseTuple(%p: DeriveFromTuple*) -> (Base,)* {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %p.ref: DeriveFromTuple* = name_ref p, %p
-// CHECK:STDOUT:   %.loc35_69.1: ref DeriveFromTuple = deref %p.ref
-// CHECK:STDOUT:   %.loc35_69.2: ref (Base,) = class_element_access %.loc35_69.1, element0
-// CHECK:STDOUT:   %.loc35_69.3: (Base,)* = address_of %.loc35_69.2
-// CHECK:STDOUT:   %.loc35_69.4: (Base,)* = converted %p.ref, %.loc35_69.3
-// CHECK:STDOUT:   return %.loc35_69.4
+// CHECK:STDOUT:   return <error>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @AccessMemberWithInvalidBaseTuple(%p: DeriveFromTuple*) -> i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %p.ref: DeriveFromTuple* = name_ref p, %p
+// CHECK:STDOUT:   %.loc59: ref DeriveFromTuple = deref %p.ref
+// CHECK:STDOUT:   %n.ref: <error> = name_ref n, <error>
+// CHECK:STDOUT:   return <error>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @ConvertToBadBaseStruct(%p: DeriveFromStruct*) -> {.a: i32, .b: i32}* {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %p.ref: DeriveFromStruct* = name_ref p, %p
-// CHECK:STDOUT:   %.loc46_82.1: ref DeriveFromStruct = deref %p.ref
-// CHECK:STDOUT:   %.loc46_82.2: ref {.a: i32, .b: i32} = class_element_access %.loc46_82.1, element0
-// CHECK:STDOUT:   %.loc46_82.3: {.a: i32, .b: i32}* = address_of %.loc46_82.2
-// CHECK:STDOUT:   %.loc46_82.4: {.a: i32, .b: i32}* = converted %p.ref, %.loc46_82.3
-// CHECK:STDOUT:   return %.loc46_82.4
+// CHECK:STDOUT:   return <error>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @AccessMemberWithInvalidBaseStruct(%p: DeriveFromStruct*) -> i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %p.ref: DeriveFromStruct* = name_ref p, %p
+// CHECK:STDOUT:   %.loc76: ref DeriveFromStruct = deref %p.ref
+// CHECK:STDOUT:   %n.ref: <error> = name_ref n, <error>
+// CHECK:STDOUT:   return <error>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @ConvertToBadBaseIncomplete(%p: DeriveFromIncomplete*) -> Incomplete* {
@@ -226,13 +324,40 @@ fn ConvertToBadBaseFinal(p: DeriveFromFinal*) -> Final* { return p; }
 // CHECK:STDOUT:   return <error>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: fn @AccessMemberWithInvalidBaseIncomplete(%p: DeriveFromIncomplete*) -> i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %p.ref: DeriveFromIncomplete* = name_ref p, %p
+// CHECK:STDOUT:   %.loc95: ref DeriveFromIncomplete = deref %p.ref
+// CHECK:STDOUT:   %n.ref: <error> = name_ref n, <error>
+// CHECK:STDOUT:   return <error>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: fn @ConvertToBadBaseFinal(%p: DeriveFromFinal*) -> Final* {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %p.ref: DeriveFromFinal* = name_ref p, %p
-// CHECK:STDOUT:   %.loc72_67.1: ref DeriveFromFinal = deref %p.ref
-// CHECK:STDOUT:   %.loc72_67.2: ref Final = class_element_access %.loc72_67.1, element0
-// CHECK:STDOUT:   %.loc72_67.3: Final* = address_of %.loc72_67.2
-// CHECK:STDOUT:   %.loc72_67.4: Final* = converted %p.ref, %.loc72_67.3
-// CHECK:STDOUT:   return %.loc72_67.4
+// CHECK:STDOUT:   %.loc106_11.1: ref DeriveFromFinal = deref %p.ref
+// CHECK:STDOUT:   %.loc106_11.2: ref Final = class_element_access %.loc106_11.1, element0
+// CHECK:STDOUT:   %.loc106_11.3: Final* = address_of %.loc106_11.2
+// CHECK:STDOUT:   %.loc106_11.4: Final* = converted %p.ref, %.loc106_11.3
+// CHECK:STDOUT:   return %.loc106_11.4
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @AccessMemberWithInvalidBaseFinal_WithMember(%p: DeriveFromFinal*) -> i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %p.ref: DeriveFromFinal* = name_ref p, %p
+// CHECK:STDOUT:   %.loc110_11: ref DeriveFromFinal = deref %p.ref
+// CHECK:STDOUT:   %.loc110_14.1: ref Final = class_element_access %.loc110_11, element0
+// CHECK:STDOUT:   %.loc110_14.2: ref Final = converted %.loc110_11, %.loc110_14.1
+// CHECK:STDOUT:   %.loc110_14.3: ref i32 = class_element_access %.loc110_14.2, element0
+// CHECK:STDOUT:   %.loc110_14.4: i32 = bind_value %.loc110_14.3
+// CHECK:STDOUT:   return %.loc110_14.4
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @AccessMemberWithInvalidBaseFinal_NoMember(%p: DeriveFromFinal*) -> i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %p.ref: DeriveFromFinal* = name_ref p, %p
+// CHECK:STDOUT:   %.loc117: ref DeriveFromFinal = deref %p.ref
+// CHECK:STDOUT:   %b.ref: <error> = name_ref b, <error>
+// CHECK:STDOUT:   return <error>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 87 - 0
toolchain/check/testdata/class/fail_base_method_define.carbon

@@ -0,0 +1,87 @@
+// 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
+//
+// AUTOUPDATE
+
+base class B {
+  fn F();
+
+  class C {
+    fn F();
+  }
+}
+
+class D {
+  extend base: B;
+}
+
+// TODO: This should be rejected.
+fn D.F() {}
+
+// CHECK:STDERR: fail_base_method_define.carbon:[[@LINE+3]]:6: ERROR: Name `C` not found.
+// CHECK:STDERR: fn D.C.F() {}
+// CHECK:STDERR:      ^
+fn D.C.F() {}
+
+// CHECK:STDOUT: --- fail_base_method_define.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.loc12_3.1: type = struct_type {}
+// CHECK:STDOUT:   %.loc12_3.2: type = tuple_type ()
+// CHECK:STDOUT:   %.loc7: type = ptr_type {}
+// CHECK:STDOUT:   %.loc17: type = struct_type {.base: B}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace {.B = %B.decl, .D = %D.decl}
+// CHECK:STDOUT:   %B.decl = class_decl @B, ()
+// CHECK:STDOUT:   %B: type = class_type @B
+// CHECK:STDOUT:   %D.decl = class_decl @D, ()
+// CHECK:STDOUT:   %D: type = class_type @D
+// CHECK:STDOUT:   %F: <function> = fn_decl @F.3
+// CHECK:STDOUT:   %.loc25: <function> = fn_decl @.1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @B {
+// CHECK:STDOUT:   %F: <function> = fn_decl @F.1
+// CHECK:STDOUT:   %C.decl = class_decl @C, ()
+// CHECK:STDOUT:   %C: type = class_type @C
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .F = %F
+// CHECK:STDOUT:   .C = %C.decl
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
+// CHECK:STDOUT:   %F: <function> = fn_decl @F.2
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .F = %F
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @D {
+// CHECK:STDOUT:   %B.ref: type = name_ref B, file.%B
+// CHECK:STDOUT:   %.loc16_17.1: type = unbound_element_type D, B
+// CHECK:STDOUT:   %.loc16_17.2: <unbound element of class D> = base_decl B, element0
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .base = %.loc16_17.2
+// CHECK:STDOUT:   .F = file.%F
+// CHECK:STDOUT:   extend name_scope1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F.1();
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F.2();
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F.3() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @.1() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 3 - 0
toolchain/check/testdata/class/fail_base_modifiers.carbon

@@ -78,6 +78,7 @@ class C4 {
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .base = %.loc13_25.2
+// CHECK:STDOUT:   extend name_scope1
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @C2 {
@@ -96,6 +97,7 @@ class C4 {
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .base = %.loc33_25.2
+// CHECK:STDOUT:   extend name_scope1
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @C4 {
@@ -105,5 +107,6 @@ class C4 {
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .base = %.loc43_24.2
+// CHECK:STDOUT:   extend name_scope1
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 2 - 0
toolchain/check/testdata/class/fail_base_repeated.carbon

@@ -69,6 +69,7 @@ class D {
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .base = %.loc11_18.2
+// CHECK:STDOUT:   extend name_scope1
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @D {
@@ -79,5 +80,6 @@ class D {
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .base = %.loc23_18.2
+// CHECK:STDOUT:   extend name_scope1
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 1 - 0
toolchain/check/testdata/class/fail_base_unbound.carbon

@@ -48,5 +48,6 @@ let b: B = C.base;
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .base = %.loc10_17.2
+// CHECK:STDOUT:   extend name_scope1
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 1 - 0
toolchain/check/testdata/class/fail_derived_to_base.carbon

@@ -83,6 +83,7 @@ fn ConvertIncomplete(p: Incomplete*) -> A2* { return p; }
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .base = %.loc16_18.2
 // CHECK:STDOUT:   .b = %b
+// CHECK:STDOUT:   extend name_scope2
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @Incomplete;

+ 1 - 0
toolchain/check/testdata/class/self_conversion.carbon

@@ -70,6 +70,7 @@ fn Call(p: Derived*) -> i32 {
 // CHECK:STDOUT:   .base = %.loc12_20.2
 // CHECK:STDOUT:   .SelfBase = %SelfBase
 // CHECK:STDOUT:   .AddrSelfBase = %AddrSelfBase
+// CHECK:STDOUT:   extend name_scope1
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @SelfBase[%self: Base]() -> i32 {

+ 3 - 3
toolchain/check/testdata/packages/fail_api_not_found.carbon

@@ -28,18 +28,18 @@ library "Bar" impl;
 // CHECK:STDOUT: --- no_api.impl.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
-// CHECK:STDOUT:   package: <namespace> = namespace {has_load_error}
+// CHECK:STDOUT:   package: <namespace> = namespace {has_error}
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- no_api_lib.impl.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
-// CHECK:STDOUT:   package: <namespace> = namespace {has_load_error}
+// CHECK:STDOUT:   package: <namespace> = namespace {has_error}
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- no_api_main_lib.impl.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
-// CHECK:STDOUT:   package: <namespace> = namespace {has_load_error}
+// CHECK:STDOUT:   package: <namespace> = namespace {has_error}
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 1 - 1
toolchain/check/testdata/packages/fail_cycle.carbon

@@ -74,7 +74,7 @@ import B;
 // CHECK:STDOUT: --- c.impl.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
-// CHECK:STDOUT:   package: <namespace> = namespace {has_load_error}
+// CHECK:STDOUT:   package: <namespace> = namespace {has_error}
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- cycle_child.carbon

+ 1 - 1
toolchain/check/testdata/packages/fail_name_with_import_failure.carbon

@@ -20,7 +20,7 @@ var a: () = A();
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
-// CHECK:STDOUT:   package: <namespace> = namespace {.a = %a, has_load_error}
+// CHECK:STDOUT:   package: <namespace> = namespace {.a = %a, has_error}
 // CHECK:STDOUT:   %.loc7_9.1: () = tuple_literal ()
 // CHECK:STDOUT:   %.loc7_9.2: type = converted %.loc7_9.1, constants.%.loc7
 // CHECK:STDOUT:   %a.var: ref () = var a

+ 1 - 0
toolchain/diagnostics/diagnostic_kind.def

@@ -174,6 +174,7 @@ CARBON_DIAGNOSTIC_KIND(DerefOfNonPointer)
 CARBON_DIAGNOSTIC_KIND(DerefOfType)
 CARBON_DIAGNOSTIC_KIND(FunctionPreviousDefinition)
 CARBON_DIAGNOSTIC_KIND(FunctionRedefinition)
+CARBON_DIAGNOSTIC_KIND(NameAmbiguousDueToExtend)
 CARBON_DIAGNOSTIC_KIND(NameNotFound)
 CARBON_DIAGNOSTIC_KIND(NameDeclDuplicate)
 CARBON_DIAGNOSTIC_KIND(NameDeclPrevious)

+ 11 - 6
toolchain/sem_ir/formatter.cpp

@@ -609,7 +609,7 @@ class Formatter {
       out_ << " {\n";
       FormatCodeBlock(class_info.body_block_id);
       out_ << "\n!members:";
-      FormatNameScope(class_info.scope_id, "", "\n  .");
+      FormatNameScope(class_info.scope_id, "", "\n  ");
       out_ << "\n}\n";
     } else {
       out_ << ";\n";
@@ -628,7 +628,7 @@ class Formatter {
       out_ << " {\n";
       FormatCodeBlock(interface_info.body_block_id);
       out_ << "\n!members:";
-      FormatNameScope(interface_info.scope_id, "", "\n  .");
+      FormatNameScope(interface_info.scope_id, "", "\n  ");
       out_ << "\n}\n";
     } else {
       out_ << ";\n";
@@ -721,14 +721,19 @@ class Formatter {
 
     llvm::ListSeparator sep(separator);
     for (auto [inst_id, name_id] : entries) {
-      out_ << sep << prefix;
+      out_ << sep << prefix << ".";
       FormatName(name_id);
       out_ << " = ";
       FormatInstName(inst_id);
     }
 
-    if (scope.has_load_error) {
-      out_ << sep << "has_load_error";
+    for (auto extended_scope_id : scope.extended_scopes) {
+      // TODO: Print this scope in a better way.
+      out_ << sep << prefix << "extend " << extended_scope_id;
+    }
+
+    if (scope.has_error) {
+      out_ << sep << prefix << "has_error";
     }
   }
 
@@ -986,7 +991,7 @@ class Formatter {
 
   auto FormatArg(NameScopeId id) -> void {
     out_ << '{';
-    FormatNameScope(id, ", ", ".");
+    FormatNameScope(id, ", ", "");
     out_ << '}';
   }
 

+ 24 - 3
toolchain/sem_ir/value_stores.h

@@ -168,9 +168,30 @@ struct NameScope {
   // Names in the scope.
   llvm::DenseMap<NameId, InstId> names;
 
-  // Whether the scope corresponds to an import that failed. There may still be
-  // names from successful imports, or the current file.
-  bool has_load_error = false;
+  // Scopes extended by this scope.
+  //
+  // TODO: A `NameScopeId` is currently insufficient to describe an extended
+  // scope in general. For example:
+  //
+  //   class A(T:! type) {
+  //     extend base: B(T*);
+  //   }
+  //
+  // needs to describe the `T*` argument.
+  //
+  // Small vector size is set to 1: we expect that there will rarely be more
+  // than a single extended scope. Currently the only kind of extended scope is
+  // a base class, and there can be only one of those per scope.
+  // TODO: Revisit this once we have more kinds of extended scope and data.
+  // TODO: Consider using something like `TinyPtrVector` for this.
+  llvm::SmallVector<NameScopeId, 1> extended_scopes;
+
+  // Whether we have diagnosed an error in a construct that would have added
+  // names to this scope. For example, this can happen if an `import` failed or
+  // an `extend` declaration was ill-formed. If true, the `names` map is
+  // assumed to be missing names as a result of the error, and no further
+  // errors are produced for lookup failures in this scope.
+  bool has_error = false;
 };
 
 // Provides a ValueStore wrapper for an API specific to name scopes.