Explorar el Código

Disallow creating instances of abstract classes (#4381)

A good first-pass, at least. (abstract adapters are rejected with this
change, though pending further language design discussion)

---------

Co-authored-by: Richard Smith <richard@metafoo.co.uk>
David Blaikie hace 1 año
padre
commit
d491387a98

+ 38 - 2
toolchain/check/context.cpp

@@ -195,6 +195,17 @@ auto Context::DiagnoseNameNotFound(SemIRLoc loc, SemIR::NameId name_id)
   emitter_->Emit(loc, NameNotFound, name_id);
 }
 
+auto Context::NoteAbstractClass(SemIR::ClassId class_id,
+                                DiagnosticBuilder& builder) -> void {
+  const auto& class_info = classes().Get(class_id);
+  CARBON_CHECK(
+      class_info.inheritance_kind == SemIR::Class::InheritanceKind::Abstract,
+      "Class is not abstract");
+  CARBON_DIAGNOSTIC(ClassAbstractHere, Note,
+                    "class was declared abstract here");
+  builder.Note(class_info.definition_id, ClassAbstractHere);
+}
+
 auto Context::NoteIncompleteClass(SemIR::ClassId class_id,
                                   DiagnosticBuilder& builder) -> void {
   const auto& class_info = classes().Get(class_id);
@@ -1128,8 +1139,33 @@ class TypeCompleter {
 }  // namespace
 
 auto Context::TryToCompleteType(SemIR::TypeId type_id,
-                                BuildDiagnosticFn diagnoser) -> bool {
-  return TypeCompleter(*this, diagnoser).Complete(type_id);
+                                BuildDiagnosticFn diagnoser,
+                                BuildDiagnosticFn abstract_diagnoser) -> bool {
+  if (!TypeCompleter(*this, diagnoser).Complete(type_id)) {
+    return false;
+  }
+
+  if (!abstract_diagnoser) {
+    return true;
+  }
+
+  if (auto class_type = types().TryGetAs<SemIR::ClassType>(type_id)) {
+    auto& class_info = classes().Get(class_type->class_id);
+    if (class_info.inheritance_kind !=
+        SemIR::Class::InheritanceKind::Abstract) {
+      return true;
+    }
+
+    auto builder = abstract_diagnoser();
+    if (!builder) {
+      return false;
+    }
+    NoteAbstractClass(class_type->class_id, builder);
+    builder.Emit();
+    return false;
+  }
+
+  return true;
 }
 
 auto Context::TryToDefineType(SemIR::TypeId type_id,

+ 12 - 5
toolchain/check/context.h

@@ -235,6 +235,10 @@ class Context {
   auto NoteIncompleteClass(SemIR::ClassId class_id, DiagnosticBuilder& builder)
       -> void;
 
+  // Adds a note to a diagnostic explaining that a class is abstract.
+  auto NoteAbstractClass(SemIR::ClassId class_id, DiagnosticBuilder& builder)
+      -> void;
+
   // Adds a note to a diagnostic explaining that an interface is not defined.
   auto NoteUndefinedInterface(SemIR::InterfaceId interface_id,
                               DiagnosticBuilder& builder) -> void;
@@ -319,7 +323,9 @@ class Context {
   // if a `diagnoser` is provided. The builder it returns will be annotated to
   // describe the reason why the type is not complete.
   auto TryToCompleteType(SemIR::TypeId type_id,
-                         BuildDiagnosticFn diagnoser = nullptr) -> bool;
+                         BuildDiagnosticFn diagnoser = nullptr,
+                         BuildDiagnosticFn abstract_diagnoser = nullptr)
+      -> bool;
 
   // Attempts to complete and define the type `type_id`. Returns `true` if the
   // type is defined, or `false` if no definition is available. A defined type
@@ -333,11 +339,12 @@ class Context {
   // Returns the type `type_id` as a complete type, or produces an incomplete
   // type error and returns an error type. This is a convenience wrapper around
   // TryToCompleteType. `diagnoser` must not be null.
-  auto AsCompleteType(SemIR::TypeId type_id, BuildDiagnosticFn diagnoser)
+  auto AsCompleteType(SemIR::TypeId type_id, BuildDiagnosticFn diagnoser,
+                      BuildDiagnosticFn abstract_diagnoser = nullptr)
       -> SemIR::TypeId {
-    CARBON_CHECK(diagnoser);
-    return TryToCompleteType(type_id, diagnoser) ? type_id
-                                                 : SemIR::TypeId::Error;
+    return TryToCompleteType(type_id, diagnoser, abstract_diagnoser)
+               ? type_id
+               : SemIR::TypeId::Error;
   }
 
   // Returns whether `type_id` represents a facet type.

+ 33 - 26
toolchain/check/convert.cpp

@@ -533,15 +533,7 @@ static auto ConvertStructToClass(Context& context, SemIR::StructType src_type,
                                  ConversionTarget target) -> SemIR::InstId {
   PendingBlock target_block(context);
   auto& dest_class_info = context.classes().Get(dest_type.class_id);
-  if (dest_class_info.inheritance_kind == SemIR::Class::Abstract) {
-    CARBON_DIAGNOSTIC(ConstructionOfAbstractClass, Error,
-                      "cannot construct instance of abstract class; "
-                      "consider using `partial {0}` instead",
-                      TypeIdAsRawType);
-    context.emitter().Emit(value_id, ConstructionOfAbstractClass,
-                           target.type_id);
-    return SemIR::InstId::BuiltinError;
-  }
+  CARBON_CHECK(dest_class_info.inheritance_kind != SemIR::Class::Abstract);
   auto object_repr_id =
       dest_class_info.GetObjectRepr(context.sem_ir(), dest_type.specific_id);
   if (object_repr_id == SemIR::TypeId::Error) {
@@ -937,23 +929,38 @@ auto Convert(Context& context, SemIR::LocId loc_id, SemIR::InstId expr_id,
   }
 
   // We can only perform initialization for complete types.
-  if (!context.TryToCompleteType(target.type_id, [&] {
-        CARBON_DIAGNOSTIC(IncompleteTypeInInit, Error,
-                          "initialization of incomplete type {0}",
-                          SemIR::TypeId);
-        CARBON_DIAGNOSTIC(IncompleteTypeInValueConversion, Error,
-                          "forming value of incomplete type {0}",
-                          SemIR::TypeId);
-        CARBON_DIAGNOSTIC(IncompleteTypeInConversion, Error,
-                          "invalid use of incomplete type {0}", SemIR::TypeId);
-        return context.emitter().Build(loc_id,
-                                       target.is_initializer()
-                                           ? IncompleteTypeInInit
-                                       : target.kind == ConversionTarget::Value
-                                           ? IncompleteTypeInValueConversion
-                                           : IncompleteTypeInConversion,
-                                       target.type_id);
-      })) {
+  if (!context.TryToCompleteType(
+          target.type_id,
+          [&] {
+            CARBON_DIAGNOSTIC(IncompleteTypeInInit, Error,
+                              "initialization of incomplete type {0}",
+                              SemIR::TypeId);
+            CARBON_DIAGNOSTIC(IncompleteTypeInValueConversion, Error,
+                              "forming value of incomplete type {0}",
+                              SemIR::TypeId);
+            CARBON_DIAGNOSTIC(IncompleteTypeInConversion, Error,
+                              "invalid use of incomplete type {0}",
+                              SemIR::TypeId);
+            assert(!target.is_initializer());
+            assert(target.kind == ConversionTarget::Value);
+            return context.emitter().Build(
+                loc_id,
+                target.is_initializer() ? IncompleteTypeInInit
+                : target.kind == ConversionTarget::Value
+                    ? IncompleteTypeInValueConversion
+                    : IncompleteTypeInConversion,
+                target.type_id);
+          },
+          [&] {
+            CARBON_DIAGNOSTIC(AbstractTypeInInit, Error,
+                              "initialization of abstract type {0}",
+                              SemIR::TypeId);
+            if (!target.is_initializer()) {
+              return context.emitter().BuildSuppressed();
+            }
+            return context.emitter().Build(loc_id, AbstractTypeInInit,
+                                           target.type_id);
+          })) {
     return SemIR::InstId::BuiltinError;
   }
 

+ 8 - 1
toolchain/check/function.cpp

@@ -81,11 +81,18 @@ auto CheckFunctionReturnType(Context& context, SemIRLoc loc,
       return context.emitter().Build(loc, IncompleteTypeInFunctionReturnType,
                                      return_info.type_id);
     };
+    auto diagnose_abstract_return_type = [&] {
+      CARBON_DIAGNOSTIC(AbstractTypeInFunctionReturnType, Error,
+                        "function returns abstract type {0}", SemIR::TypeId);
+      return context.emitter().Build(loc, AbstractTypeInFunctionReturnType,
+                                     return_info.type_id);
+    };
 
     // TODO: Consider suppressing the diagnostic if we've already diagnosed a
     // definition or call to this function.
     if (context.TryToCompleteType(return_info.type_id,
-                                  diagnose_incomplete_return_type)) {
+                                  diagnose_incomplete_return_type,
+                                  diagnose_abstract_return_type)) {
       return_info = SemIR::ReturnTypeInfo::ForFunction(context.sem_ir(),
                                                        function, specific_id);
     }

+ 22 - 10
toolchain/check/handle_binding_pattern.cpp

@@ -100,16 +100,28 @@ static auto HandleAnyBindingPattern(Context& context, Parse::NodeId node_id,
 
       // A `var` declaration at class scope introduces a field.
       auto parent_class_decl = context.GetCurrentScopeAs<SemIR::ClassDecl>();
-      cast_type_id = context.AsCompleteType(cast_type_id, [&] {
-        CARBON_DIAGNOSTIC(IncompleteTypeInVarDecl, Error,
-                          "{0} has incomplete type {1}", llvm::StringLiteral,
-                          InstIdAsType);
-        return context.emitter().Build(type_node, IncompleteTypeInVarDecl,
-                                       parent_class_decl
-                                           ? llvm::StringLiteral("Field")
-                                           : llvm::StringLiteral("Variable"),
-                                       cast_type_inst_id);
-      });
+      cast_type_id = context.AsCompleteType(
+          cast_type_id,
+          [&] {
+            CARBON_DIAGNOSTIC(IncompleteTypeInVarDecl, Error,
+                              "{0} has incomplete type {1}",
+                              llvm::StringLiteral, SemIR::TypeId);
+            return context.emitter().Build(
+                type_node, IncompleteTypeInVarDecl,
+                parent_class_decl ? llvm::StringLiteral("field")
+                                  : llvm::StringLiteral("variable"),
+                cast_type_id);
+          },
+          [&] {
+            CARBON_DIAGNOSTIC(AbstractTypeInVarDecl, Error,
+                              "{0} has abstract type {1}", llvm::StringLiteral,
+                              SemIR::TypeId);
+            return context.emitter().Build(
+                type_node, AbstractTypeInVarDecl,
+                parent_class_decl ? llvm::StringLiteral("field")
+                                  : llvm::StringLiteral("variable"),
+                cast_type_id);
+          });
       if (parent_class_decl) {
         CARBON_CHECK(context_node_kind == Parse::NodeKind::VariableIntroducer,
                      "`returned var` at class scope");

+ 18 - 8
toolchain/check/handle_class.cpp

@@ -383,14 +383,24 @@ auto HandleParseNode(Context& context, Parse::AdaptDeclId node_id) -> bool {
     return true;
   }
 
-  auto [adapted_type_inst_id, adapted_type_id] =
-      ExprAsType(context, node_id, adapted_type_expr_id);
-  adapted_type_id = context.AsCompleteType(adapted_type_id, [&] {
-    CARBON_DIAGNOSTIC(IncompleteTypeInAdaptDecl, Error,
-                      "adapted type {0} is an incomplete type", InstIdAsType);
-    return context.emitter().Build(node_id, IncompleteTypeInAdaptDecl,
-                                   adapted_type_inst_id);
-  });
+  auto adapted_type_id =
+      ExprAsType(context, node_id, adapted_type_expr_id).type_id;
+  adapted_type_id = context.AsCompleteType(
+      adapted_type_id,
+      [&] {
+        CARBON_DIAGNOSTIC(IncompleteTypeInAdaptDecl, Error,
+                          "adapted type {0} is an incomplete type",
+                          SemIR::TypeId);
+        return context.emitter().Build(node_id, IncompleteTypeInAdaptDecl,
+                                       adapted_type_id);
+      },
+      [&] {
+        CARBON_DIAGNOSTIC(AbstractTypeInAdaptDecl, Error,
+                          "adapted type {0} is an abstract type",
+                          SemIR::TypeId);
+        return context.emitter().Build(node_id, AbstractTypeInAdaptDecl,
+                                       adapted_type_id);
+      });
 
   // Build a SemIR representation for the declaration.
   class_info.adapt_id = context.AddInst<SemIR::AdaptDecl>(

+ 1 - 1
toolchain/check/testdata/array/fail_incomplete_element.carbon

@@ -10,7 +10,7 @@
 
 class Incomplete;
 
-// CHECK:STDERR: fail_incomplete_element.carbon:[[@LINE+6]]:8: error: Variable has incomplete type `[Incomplete; 1]`
+// CHECK:STDERR: fail_incomplete_element.carbon:[[@LINE+6]]:8: error: variable has incomplete type `[Incomplete; 1]`
 // CHECK:STDERR: var a: [Incomplete; 1];
 // CHECK:STDERR:        ^~~~~~~~~~~~~~~
 // CHECK:STDERR: fail_incomplete_element.carbon:[[@LINE-5]]:1: note: class was forward declared here

+ 1 - 1
toolchain/check/testdata/class/cross_package_import.carbon

@@ -48,7 +48,7 @@ library "[[@TEST_NAME]]";
 
 import Other library "other_extern";
 
-// CHECK:STDERR: fail_extern.carbon:[[@LINE+8]]:8: error: Variable has incomplete type `C`
+// CHECK:STDERR: fail_extern.carbon:[[@LINE+8]]:8: error: variable has incomplete type `C`
 // CHECK:STDERR: var c: Other.C = {};
 // CHECK:STDERR:        ^~~~~~~
 // CHECK:STDERR: fail_extern.carbon:[[@LINE-5]]:1: in import

+ 697 - 80
toolchain/check/testdata/class/fail_abstract.carbon

@@ -8,10 +8,90 @@
 // TIP: To dump output, run:
 // TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/class/fail_abstract.carbon
 
+// --- fail_abstract_field.carbon
+
+library "[[@TEST_NAME]]";
+
+abstract class Abstract {
+}
+
+class Contains {
+  // CHECK:STDERR: fail_abstract_field.carbon:[[@LINE+7]]:10: error: field has abstract type `Abstract`
+  // CHECK:STDERR:   var a: Abstract;
+  // CHECK:STDERR:          ^~~~~~~~
+  // CHECK:STDERR: fail_abstract_field.carbon:[[@LINE-7]]:1: note: class was declared abstract here
+  // CHECK:STDERR: abstract class Abstract {
+  // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR:
+  var a: Abstract;
+}
+
+// --- fail_abstract_var.carbon
+
+library "[[@TEST_NAME]]";
+
+abstract class Abstract {
+}
+
+fn Var() {
+  // CHECK:STDERR: fail_abstract_var.carbon:[[@LINE+7]]:10: error: variable has abstract type `Abstract`
+  // CHECK:STDERR:   var v: Abstract;
+  // CHECK:STDERR:          ^~~~~~~~
+  // CHECK:STDERR: fail_abstract_var.carbon:[[@LINE-7]]:1: note: class was declared abstract here
+  // CHECK:STDERR: abstract class Abstract {
+  // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR:
+  var v: Abstract;
+}
+
+// --- abstract_let.carbon
+
+library "[[@TEST_NAME]]";
+
+abstract class Abstract {
+}
+
+fn F(a: Abstract) {
+  let l: Abstract = a;
+}
+
+// --- fail_abstract_adapter.carbon
+
+library "[[@TEST_NAME]]";
+
+abstract class Abstract {
+}
+
+class Adapter {
+  // TODO(#4387): This should probably be valid
+  // CHECK:STDERR: fail_abstract_adapter.carbon:[[@LINE+7]]:3: error: adapted type `Abstract` is an abstract type
+  // CHECK:STDERR:   adapt Abstract;
+  // CHECK:STDERR:   ^~~~~~~~~~~~~~~
+  // CHECK:STDERR: fail_abstract_adapter.carbon:[[@LINE-8]]:1: note: class was declared abstract here
+  // CHECK:STDERR: abstract class Abstract {
+  // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR:
+  adapt Abstract;
+}
+
+// --- define_and_call_abstract_param.carbon
+
+library "[[@TEST_NAME]]";
+
+abstract class Abstract {
+}
+
+fn Param(a: Abstract);
+
+fn Call(p: Abstract) {
+  Param(p);
+}
+
+// --- fail_todo_return_nonabstract_derived.carbon
+
+library "[[@TEST_NAME]]";
 
-// --- fail_todo_rejects_valid_abstract_subobject_construction.carbon
 abstract class Abstract {
-  var a: i32;
 }
 
 class Derived {
@@ -22,57 +102,396 @@ class Derived {
 
 fn Make() -> Derived {
   // TODO: This should be valid, and should construct an instance of `partial Abstract` as the base.
-  // CHECK:STDERR: fail_todo_rejects_valid_abstract_subobject_construction.carbon:[[@LINE+6]]:19: error: cannot construct instance of abstract class; consider using `partial Abstract` instead
+  // CHECK:STDERR: fail_todo_return_nonabstract_derived.carbon:[[@LINE+7]]:10: error: initialization of abstract type `Abstract`
   // CHECK:STDERR:   return {.base = {.a = 1}, .d = 7};
-  // CHECK:STDERR:                   ^~~~~~~~
-  // CHECK:STDERR:
-  // CHECK:STDERR: fail_todo_rejects_valid_abstract_subobject_access.carbon: error: `Main//default` previously provided by `fail_todo_rejects_valid_abstract_subobject_construction.carbon`
+  // CHECK:STDERR:          ^~~~~~~~~~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR: fail_todo_return_nonabstract_derived.carbon:[[@LINE-14]]:1: note: class was declared abstract here
+  // CHECK:STDERR: abstract class Abstract {
+  // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~
   // CHECK:STDERR:
   return {.base = {.a = 1}, .d = 7};
 }
 
-// --- fail_todo_rejects_valid_abstract_subobject_access.carbon
+// --- fail_return_abstract.carbon
+
+library "[[@TEST_NAME]]";
+
+abstract class Abstract {
+}
+
+class Derived {
+  extend base: Abstract;
+
+  var d: i32;
+}
+
+fn Return(a: Abstract) -> Abstract {
+  // FIXME: Seems like this would be better off failing with "function returns abstract type" here instead of this \/
+  // CHECK:STDERR: fail_return_abstract.carbon:[[@LINE+7]]:3: error: initialization of abstract type `Abstract`
+  // CHECK:STDERR:   return a;
+  // CHECK:STDERR:   ^~~~~~~~~
+  // CHECK:STDERR: fail_return_abstract.carbon:[[@LINE-14]]:1: note: class was declared abstract here
+  // CHECK:STDERR: abstract class Abstract {
+  // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR:
+  return a;
+}
+
+// --- access_abstract_subobject.carbon
+
+library "[[@TEST_NAME]]";
+
+abstract class Abstract {
+  var a: i32;
+}
+
+class Derived {
+  extend base: Abstract;
 
-// CHECK:STDERR: fail_todo_rejects_valid_abstract_subobject_access.carbon:[[@LINE+6]]:14: error: name `Derived` not found
-// CHECK:STDERR: fn Access(d: Derived) -> (i32, i32) {
-// CHECK:STDERR:              ^~~~~~~
-// CHECK:STDERR:
-// CHECK:STDERR: fail_abstract_decl.carbon: error: `Main//default` previously provided by `fail_todo_rejects_valid_abstract_subobject_construction.carbon`
-// CHECK:STDERR:
-fn Access(d: Derived) -> (i32, i32) {
-  return (d.d, d.base.a);
+  var d: i32;
 }
 
-// --- fail_abstract_decl.carbon
-// CHECK:STDERR: fail_abstract_decl.carbon:[[@LINE+3]]:1: error: `abstract` not allowed on `class` declaration, only definition
-// CHECK:STDERR: abstract class AbstractDecl;
-// CHECK:STDERR: ^~~~~~~~
-abstract class AbstractDecl;
+fn Access(d: Derived) -> i32 {
+  return d.base.a;
+}
+
+// --- abstract_let_temporary.carbon
+
+library "[[@TEST_NAME]]";
+
+abstract class Abstract {
+}
+
+fn F() {
+  let l: Abstract = {};
+}
+
+// --- fail_call_abstract_return.carbon
+
+library "[[@TEST_NAME]]";
+
+abstract class Abstract {
+}
+
+fn ReturnAbstract() -> Abstract;
+
+fn CallReturnAbstract() {
+  // CHECK:STDERR: fail_call_abstract_return.carbon:[[@LINE+9]]:3: error: function returns abstract type `Abstract`
+  // CHECK:STDERR:   ReturnAbstract();
+  // CHECK:STDERR:   ^~~~~~~~~~~~~~
+  // CHECK:STDERR: fail_call_abstract_return.carbon:[[@LINE-9]]:1: note: class was declared abstract here
+  // CHECK:STDERR: abstract class Abstract {
+  // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR: fail_call_abstract_return.carbon:[[@LINE-9]]:21: note: return type declared here
+  // CHECK:STDERR: fn ReturnAbstract() -> Abstract;
+  // CHECK:STDERR:                     ^~~~~~~~~~~
+  ReturnAbstract();
+}
 
-// CHECK:STDOUT: --- fail_todo_rejects_valid_abstract_subobject_construction.carbon
+// CHECK:STDOUT: --- fail_abstract_field.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %Abstract: type = class_type @Abstract [template]
+// CHECK:STDOUT:   %.1: type = struct_type {} [template]
+// CHECK:STDOUT:   %.2: <witness> = complete_type_witness %.1 [template]
+// CHECK:STDOUT:   %Contains: type = class_type @Contains [template]
+// CHECK:STDOUT:   %.3: type = tuple_type () [template]
+// CHECK:STDOUT:   %.4: type = ptr_type %.1 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Core: <namespace> = namespace file.%Core.import, [template] {
+// CHECK:STDOUT:     import Core//prelude
+// CHECK:STDOUT:     import Core//prelude/operators
+// CHECK:STDOUT:     import Core//prelude/types
+// CHECK:STDOUT:     import Core//prelude/operators/arithmetic
+// CHECK:STDOUT:     import Core//prelude/operators/as
+// CHECK:STDOUT:     import Core//prelude/operators/bitwise
+// CHECK:STDOUT:     import Core//prelude/operators/comparison
+// CHECK:STDOUT:     import Core//prelude/types/bool
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = imports.%Core
+// CHECK:STDOUT:     .Abstract = %Abstract.decl
+// CHECK:STDOUT:     .Contains = %Contains.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core.import = import Core
+// CHECK:STDOUT:   %Abstract.decl: type = class_decl @Abstract [template = constants.%Abstract] {} {}
+// CHECK:STDOUT:   %Contains.decl: type = class_decl @Contains [template = constants.%Contains] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Abstract {
+// CHECK:STDOUT:   %.loc5: <witness> = complete_type_witness %.1 [template = constants.%.2]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%Abstract
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Contains {
+// CHECK:STDOUT:   %Abstract.ref: type = name_ref Abstract, file.%Abstract.decl [template = constants.%Abstract]
+// CHECK:STDOUT:   %.loc15: <error> = field_decl a, element0 [template]
+// CHECK:STDOUT:   %.loc16: <witness> = complete_type_witness <error> [template = <error>]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%Contains
+// CHECK:STDOUT:   .a = %.loc15
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_abstract_var.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %Abstract: type = class_type @Abstract [template]
+// CHECK:STDOUT:   %.1: type = struct_type {} [template]
+// CHECK:STDOUT:   %.2: <witness> = complete_type_witness %.1 [template]
+// CHECK:STDOUT:   %Var.type: type = fn_type @Var [template]
+// CHECK:STDOUT:   %.3: type = tuple_type () [template]
+// CHECK:STDOUT:   %Var: %Var.type = struct_value () [template]
+// CHECK:STDOUT:   %.4: type = ptr_type %.1 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Core: <namespace> = namespace file.%Core.import, [template] {
+// CHECK:STDOUT:     import Core//prelude
+// CHECK:STDOUT:     import Core//prelude/operators
+// CHECK:STDOUT:     import Core//prelude/types
+// CHECK:STDOUT:     import Core//prelude/operators/arithmetic
+// CHECK:STDOUT:     import Core//prelude/operators/as
+// CHECK:STDOUT:     import Core//prelude/operators/bitwise
+// CHECK:STDOUT:     import Core//prelude/operators/comparison
+// CHECK:STDOUT:     import Core//prelude/types/bool
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = imports.%Core
+// CHECK:STDOUT:     .Abstract = %Abstract.decl
+// CHECK:STDOUT:     .Var = %Var.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core.import = import Core
+// CHECK:STDOUT:   %Abstract.decl: type = class_decl @Abstract [template = constants.%Abstract] {} {}
+// CHECK:STDOUT:   %Var.decl: %Var.type = fn_decl @Var [template = constants.%Var] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Abstract {
+// CHECK:STDOUT:   %.loc5: <witness> = complete_type_witness %.1 [template = constants.%.2]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%Abstract
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Var() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Abstract.ref: type = name_ref Abstract, file.%Abstract.decl [template = constants.%Abstract]
+// CHECK:STDOUT:   %v.var: ref <error> = var v
+// CHECK:STDOUT:   %v: ref <error> = bind_name v, %v.var
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- abstract_let.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %Abstract: type = class_type @Abstract [template]
+// CHECK:STDOUT:   %.1: type = struct_type {} [template]
+// CHECK:STDOUT:   %.2: <witness> = complete_type_witness %.1 [template]
+// CHECK:STDOUT:   %F.type: type = fn_type @F [template]
+// CHECK:STDOUT:   %.3: type = tuple_type () [template]
+// CHECK:STDOUT:   %F: %F.type = struct_value () [template]
+// CHECK:STDOUT:   %.4: type = ptr_type %.1 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Core: <namespace> = namespace file.%Core.import, [template] {
+// CHECK:STDOUT:     import Core//prelude
+// CHECK:STDOUT:     import Core//prelude/operators
+// CHECK:STDOUT:     import Core//prelude/types
+// CHECK:STDOUT:     import Core//prelude/operators/arithmetic
+// CHECK:STDOUT:     import Core//prelude/operators/as
+// CHECK:STDOUT:     import Core//prelude/operators/bitwise
+// CHECK:STDOUT:     import Core//prelude/operators/comparison
+// CHECK:STDOUT:     import Core//prelude/types/bool
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = imports.%Core
+// CHECK:STDOUT:     .Abstract = %Abstract.decl
+// CHECK:STDOUT:     .F = %F.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core.import = import Core
+// CHECK:STDOUT:   %Abstract.decl: type = class_decl @Abstract [template = constants.%Abstract] {} {}
+// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [template = constants.%F] {
+// CHECK:STDOUT:     %a.patt: %Abstract = binding_pattern a
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %Abstract.ref.loc7: type = name_ref Abstract, file.%Abstract.decl [template = constants.%Abstract]
+// CHECK:STDOUT:     %a.param: %Abstract = param a, runtime_param0
+// CHECK:STDOUT:     %a: %Abstract = bind_name a, %a.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Abstract {
+// CHECK:STDOUT:   %.loc5: <witness> = complete_type_witness %.1 [template = constants.%.2]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%Abstract
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F(%a: %Abstract) {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Abstract.ref.loc8: type = name_ref Abstract, file.%Abstract.decl [template = constants.%Abstract]
+// CHECK:STDOUT:   %a.ref: %Abstract = name_ref a, %a
+// CHECK:STDOUT:   %l: %Abstract = bind_name l, <error>
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_abstract_adapter.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %Abstract: type = class_type @Abstract [template]
+// CHECK:STDOUT:   %.1: type = struct_type {} [template]
+// CHECK:STDOUT:   %.2: <witness> = complete_type_witness %.1 [template]
+// CHECK:STDOUT:   %Adapter: type = class_type @Adapter [template]
+// CHECK:STDOUT:   %.3: type = tuple_type () [template]
+// CHECK:STDOUT:   %.4: type = ptr_type %.1 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Core: <namespace> = namespace file.%Core.import, [template] {
+// CHECK:STDOUT:     import Core//prelude
+// CHECK:STDOUT:     import Core//prelude/operators
+// CHECK:STDOUT:     import Core//prelude/types
+// CHECK:STDOUT:     import Core//prelude/operators/arithmetic
+// CHECK:STDOUT:     import Core//prelude/operators/as
+// CHECK:STDOUT:     import Core//prelude/operators/bitwise
+// CHECK:STDOUT:     import Core//prelude/operators/comparison
+// CHECK:STDOUT:     import Core//prelude/types/bool
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = imports.%Core
+// CHECK:STDOUT:     .Abstract = %Abstract.decl
+// CHECK:STDOUT:     .Adapter = %Adapter.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core.import = import Core
+// CHECK:STDOUT:   %Abstract.decl: type = class_decl @Abstract [template = constants.%Abstract] {} {}
+// CHECK:STDOUT:   %Adapter.decl: type = class_decl @Adapter [template = constants.%Adapter] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Abstract {
+// CHECK:STDOUT:   %.loc5: <witness> = complete_type_witness %.1 [template = constants.%.2]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%Abstract
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Adapter {
+// CHECK:STDOUT:   %Abstract.ref: type = name_ref Abstract, file.%Abstract.decl [template = constants.%Abstract]
+// CHECK:STDOUT:   adapt_decl <error>
+// CHECK:STDOUT:   %.loc17: <witness> = complete_type_witness <error> [template = <error>]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%Adapter
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- define_and_call_abstract_param.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %Abstract: type = class_type @Abstract [template]
+// CHECK:STDOUT:   %.1: type = struct_type {} [template]
+// CHECK:STDOUT:   %.2: <witness> = complete_type_witness %.1 [template]
+// CHECK:STDOUT:   %Param.type: type = fn_type @Param [template]
+// CHECK:STDOUT:   %.3: type = tuple_type () [template]
+// CHECK:STDOUT:   %Param: %Param.type = struct_value () [template]
+// CHECK:STDOUT:   %Call.type: type = fn_type @Call [template]
+// CHECK:STDOUT:   %Call: %Call.type = struct_value () [template]
+// CHECK:STDOUT:   %.4: type = ptr_type %.1 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Core: <namespace> = namespace file.%Core.import, [template] {
+// CHECK:STDOUT:     import Core//prelude
+// CHECK:STDOUT:     import Core//prelude/operators
+// CHECK:STDOUT:     import Core//prelude/types
+// CHECK:STDOUT:     import Core//prelude/operators/arithmetic
+// CHECK:STDOUT:     import Core//prelude/operators/as
+// CHECK:STDOUT:     import Core//prelude/operators/bitwise
+// CHECK:STDOUT:     import Core//prelude/operators/comparison
+// CHECK:STDOUT:     import Core//prelude/types/bool
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = imports.%Core
+// CHECK:STDOUT:     .Abstract = %Abstract.decl
+// CHECK:STDOUT:     .Param = %Param.decl
+// CHECK:STDOUT:     .Call = %Call.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core.import = import Core
+// CHECK:STDOUT:   %Abstract.decl: type = class_decl @Abstract [template = constants.%Abstract] {} {}
+// CHECK:STDOUT:   %Param.decl: %Param.type = fn_decl @Param [template = constants.%Param] {
+// CHECK:STDOUT:     %a.patt: %Abstract = binding_pattern a
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %Abstract.ref: type = name_ref Abstract, file.%Abstract.decl [template = constants.%Abstract]
+// CHECK:STDOUT:     %a.param: %Abstract = param a, runtime_param0
+// CHECK:STDOUT:     %a: %Abstract = bind_name a, %a.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Call.decl: %Call.type = fn_decl @Call [template = constants.%Call] {
+// CHECK:STDOUT:     %p.patt: %Abstract = binding_pattern p
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %Abstract.ref: type = name_ref Abstract, file.%Abstract.decl [template = constants.%Abstract]
+// CHECK:STDOUT:     %p.param: %Abstract = param p, runtime_param0
+// CHECK:STDOUT:     %p: %Abstract = bind_name p, %p.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Abstract {
+// CHECK:STDOUT:   %.loc5: <witness> = complete_type_witness %.1 [template = constants.%.2]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%Abstract
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Param(%a: %Abstract);
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Call(%p: %Abstract) {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Param.ref: %Param.type = name_ref Param, file.%Param.decl [template = constants.%Param]
+// CHECK:STDOUT:   %p.ref: %Abstract = name_ref p, %p
+// CHECK:STDOUT:   %Param.call: init %.3 = call %Param.ref(<invalid>) [template = <error>]
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_todo_return_nonabstract_derived.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %Abstract: type = class_type @Abstract [template]
+// CHECK:STDOUT:   %.1: type = struct_type {} [template]
+// CHECK:STDOUT:   %.2: <witness> = complete_type_witness %.1 [template]
+// CHECK:STDOUT:   %Derived: type = class_type @Derived [template]
+// CHECK:STDOUT:   %.3: type = tuple_type () [template]
+// CHECK:STDOUT:   %.4: type = ptr_type %.1 [template]
+// CHECK:STDOUT:   %.5: type = unbound_element_type %Derived, %Abstract [template]
 // CHECK:STDOUT:   %Int32.type: type = fn_type @Int32 [template]
-// CHECK:STDOUT:   %.1: type = tuple_type () [template]
 // CHECK:STDOUT:   %Int32: %Int32.type = struct_value () [template]
-// CHECK:STDOUT:   %.2: type = unbound_element_type %Abstract, i32 [template]
-// CHECK:STDOUT:   %.3: type = struct_type {.a: i32} [template]
-// CHECK:STDOUT:   %.4: <witness> = complete_type_witness %.3 [template]
-// CHECK:STDOUT:   %Derived: type = class_type @Derived [template]
-// CHECK:STDOUT:   %.5: type = ptr_type %.3 [template]
-// CHECK:STDOUT:   %.6: type = unbound_element_type %Derived, %Abstract [template]
-// CHECK:STDOUT:   %.7: type = unbound_element_type %Derived, i32 [template]
-// CHECK:STDOUT:   %.8: type = struct_type {.base: %Abstract, .d: i32} [template]
-// CHECK:STDOUT:   %.9: <witness> = complete_type_witness %.8 [template]
+// CHECK:STDOUT:   %.6: type = unbound_element_type %Derived, i32 [template]
+// CHECK:STDOUT:   %.7: type = struct_type {.base: %Abstract, .d: i32} [template]
+// CHECK:STDOUT:   %.8: <witness> = complete_type_witness %.7 [template]
 // CHECK:STDOUT:   %Make.type: type = fn_type @Make [template]
 // CHECK:STDOUT:   %Make: %Make.type = struct_value () [template]
-// CHECK:STDOUT:   %.10: type = struct_type {.base: %.5, .d: i32} [template]
-// CHECK:STDOUT:   %.11: type = ptr_type %.10 [template]
-// CHECK:STDOUT:   %.12: type = ptr_type %.8 [template]
-// CHECK:STDOUT:   %.13: i32 = int_literal 1 [template]
+// CHECK:STDOUT:   %.9: type = struct_type {.base: %.4, .d: i32} [template]
+// CHECK:STDOUT:   %.10: type = ptr_type %.9 [template]
+// CHECK:STDOUT:   %.11: type = ptr_type %.7 [template]
+// CHECK:STDOUT:   %.12: i32 = int_literal 1 [template]
+// CHECK:STDOUT:   %.13: type = struct_type {.a: i32} [template]
 // CHECK:STDOUT:   %.14: i32 = int_literal 7 [template]
-// CHECK:STDOUT:   %.15: type = struct_type {.base: %.3, .d: i32} [template]
+// CHECK:STDOUT:   %.15: type = struct_type {.base: %.13, .d: i32} [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
@@ -107,30 +526,25 @@ abstract class AbstractDecl;
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @Abstract {
-// CHECK:STDOUT:   %int.make_type_32: init type = call constants.%Int32() [template = i32]
-// CHECK:STDOUT:   %.loc2_10.1: type = value_of_initializer %int.make_type_32 [template = i32]
-// CHECK:STDOUT:   %.loc2_10.2: type = converted %int.make_type_32, %.loc2_10.1 [template = i32]
-// CHECK:STDOUT:   %.loc2_8: %.2 = field_decl a, element0 [template]
-// CHECK:STDOUT:   %.loc3: <witness> = complete_type_witness %.3 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc5: <witness> = complete_type_witness %.1 [template = constants.%.2]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = constants.%Abstract
-// CHECK:STDOUT:   .a = %.loc2_8
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @Derived {
 // CHECK:STDOUT:   %Abstract.ref: type = name_ref Abstract, file.%Abstract.decl [template = constants.%Abstract]
-// CHECK:STDOUT:   %.loc6: %.6 = base_decl %Abstract, element0 [template]
+// CHECK:STDOUT:   %.loc8: %.5 = base_decl %Abstract, element0 [template]
 // CHECK:STDOUT:   %int.make_type_32: init type = call constants.%Int32() [template = i32]
-// CHECK:STDOUT:   %.loc8_10.1: type = value_of_initializer %int.make_type_32 [template = i32]
-// CHECK:STDOUT:   %.loc8_10.2: type = converted %int.make_type_32, %.loc8_10.1 [template = i32]
-// CHECK:STDOUT:   %.loc8_8: %.7 = field_decl d, element1 [template]
-// CHECK:STDOUT:   %.loc9: <witness> = complete_type_witness %.8 [template = constants.%.9]
+// CHECK:STDOUT:   %.loc10_10.1: type = value_of_initializer %int.make_type_32 [template = i32]
+// CHECK:STDOUT:   %.loc10_10.2: type = converted %int.make_type_32, %.loc10_10.1 [template = i32]
+// CHECK:STDOUT:   %.loc10_8: %.6 = field_decl d, element1 [template]
+// CHECK:STDOUT:   %.loc11: <witness> = complete_type_witness %.7 [template = constants.%.8]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = constants.%Derived
-// CHECK:STDOUT:   .base = %.loc6
-// CHECK:STDOUT:   .d = %.loc8_8
+// CHECK:STDOUT:   .base = %.loc8
+// CHECK:STDOUT:   .d = %.loc10_8
 // CHECK:STDOUT:   extend name_scope2
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -138,24 +552,120 @@ abstract class AbstractDecl;
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Make() -> %return: %Derived {
 // CHECK:STDOUT: !entry:
-// CHECK:STDOUT:   %.loc19_25: i32 = int_literal 1 [template = constants.%.13]
-// CHECK:STDOUT:   %.loc19_26: %.3 = struct_literal (%.loc19_25)
-// CHECK:STDOUT:   %.loc19_34: i32 = int_literal 7 [template = constants.%.14]
-// CHECK:STDOUT:   %.loc19_35: %.15 = struct_literal (%.loc19_26, %.loc19_34)
+// CHECK:STDOUT:   %.loc22_25: i32 = int_literal 1 [template = constants.%.12]
+// CHECK:STDOUT:   %.loc22_26: %.13 = struct_literal (%.loc22_25)
+// CHECK:STDOUT:   %.loc22_34: i32 = int_literal 7 [template = constants.%.14]
+// CHECK:STDOUT:   %.loc22_35: %.15 = struct_literal (%.loc22_26, %.loc22_34)
 // CHECK:STDOUT:   return <error> to %return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: --- fail_todo_rejects_valid_abstract_subobject_access.carbon
+// CHECK:STDOUT: --- fail_return_abstract.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %Abstract: type = class_type @Abstract [template]
+// CHECK:STDOUT:   %.1: type = struct_type {} [template]
+// CHECK:STDOUT:   %.2: <witness> = complete_type_witness %.1 [template]
+// CHECK:STDOUT:   %Derived: type = class_type @Derived [template]
+// CHECK:STDOUT:   %.3: type = tuple_type () [template]
+// CHECK:STDOUT:   %.4: type = ptr_type %.1 [template]
+// CHECK:STDOUT:   %.5: type = unbound_element_type %Derived, %Abstract [template]
+// CHECK:STDOUT:   %Int32.type: type = fn_type @Int32 [template]
+// CHECK:STDOUT:   %Int32: %Int32.type = struct_value () [template]
+// CHECK:STDOUT:   %.6: type = unbound_element_type %Derived, i32 [template]
+// CHECK:STDOUT:   %.7: type = struct_type {.base: %Abstract, .d: i32} [template]
+// CHECK:STDOUT:   %.8: <witness> = complete_type_witness %.7 [template]
+// CHECK:STDOUT:   %Return.type: type = fn_type @Return [template]
+// CHECK:STDOUT:   %Return: %Return.type = struct_value () [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Core: <namespace> = namespace file.%Core.import, [template] {
+// CHECK:STDOUT:     .Int32 = %import_ref
+// CHECK:STDOUT:     import Core//prelude
+// CHECK:STDOUT:     import Core//prelude/operators
+// CHECK:STDOUT:     import Core//prelude/types
+// CHECK:STDOUT:     import Core//prelude/operators/arithmetic
+// CHECK:STDOUT:     import Core//prelude/operators/as
+// CHECK:STDOUT:     import Core//prelude/operators/bitwise
+// CHECK:STDOUT:     import Core//prelude/operators/comparison
+// CHECK:STDOUT:     import Core//prelude/types/bool
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref: %Int32.type = import_ref Core//prelude/types, inst+4, loaded [template = constants.%Int32]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = imports.%Core
+// CHECK:STDOUT:     .Abstract = %Abstract.decl
+// CHECK:STDOUT:     .Derived = %Derived.decl
+// CHECK:STDOUT:     .Return = %Return.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core.import = import Core
+// CHECK:STDOUT:   %Abstract.decl: type = class_decl @Abstract [template = constants.%Abstract] {} {}
+// CHECK:STDOUT:   %Derived.decl: type = class_decl @Derived [template = constants.%Derived] {} {}
+// CHECK:STDOUT:   %Return.decl: %Return.type = fn_decl @Return [template = constants.%Return] {
+// CHECK:STDOUT:     %a.patt: %Abstract = binding_pattern a
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %Abstract.ref.loc13_14: type = name_ref Abstract, file.%Abstract.decl [template = constants.%Abstract]
+// CHECK:STDOUT:     %a.param: %Abstract = param a, runtime_param0
+// CHECK:STDOUT:     %a: %Abstract = bind_name a, %a.param
+// CHECK:STDOUT:     %Abstract.ref.loc13_27: type = name_ref Abstract, file.%Abstract.decl [template = constants.%Abstract]
+// CHECK:STDOUT:     %return: ref %Abstract = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Abstract {
+// CHECK:STDOUT:   %.loc5: <witness> = complete_type_witness %.1 [template = constants.%.2]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%Abstract
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Derived {
+// CHECK:STDOUT:   %Abstract.ref: type = name_ref Abstract, file.%Abstract.decl [template = constants.%Abstract]
+// CHECK:STDOUT:   %.loc8: %.5 = base_decl %Abstract, element0 [template]
+// CHECK:STDOUT:   %int.make_type_32: init type = call constants.%Int32() [template = i32]
+// CHECK:STDOUT:   %.loc10_10.1: type = value_of_initializer %int.make_type_32 [template = i32]
+// CHECK:STDOUT:   %.loc10_10.2: type = converted %int.make_type_32, %.loc10_10.1 [template = i32]
+// CHECK:STDOUT:   %.loc10_8: %.6 = field_decl d, element1 [template]
+// CHECK:STDOUT:   %.loc11: <witness> = complete_type_witness %.7 [template = constants.%.8]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%Derived
+// CHECK:STDOUT:   .base = %.loc8
+// CHECK:STDOUT:   .d = %.loc10_8
+// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Return(%a: %Abstract) -> %return: %Abstract {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %a.ref: %Abstract = name_ref a, %a
+// CHECK:STDOUT:   return <error> to %return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- access_abstract_subobject.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %Abstract: type = class_type @Abstract [template]
 // CHECK:STDOUT:   %Int32.type: type = fn_type @Int32 [template]
 // CHECK:STDOUT:   %.1: type = tuple_type () [template]
 // CHECK:STDOUT:   %Int32: %Int32.type = struct_value () [template]
-// CHECK:STDOUT:   %.2: type = tuple_type (type, type) [template]
-// CHECK:STDOUT:   %.3: type = tuple_type (i32, i32) [template]
+// CHECK:STDOUT:   %.2: type = unbound_element_type %Abstract, i32 [template]
+// CHECK:STDOUT:   %.3: type = struct_type {.a: i32} [template]
+// CHECK:STDOUT:   %.4: <witness> = complete_type_witness %.3 [template]
+// CHECK:STDOUT:   %Derived: type = class_type @Derived [template]
+// CHECK:STDOUT:   %.5: type = ptr_type %.3 [template]
+// CHECK:STDOUT:   %.6: type = unbound_element_type %Derived, %Abstract [template]
+// CHECK:STDOUT:   %.7: type = unbound_element_type %Derived, i32 [template]
+// CHECK:STDOUT:   %.8: type = struct_type {.base: %Abstract, .d: i32} [template]
+// CHECK:STDOUT:   %.9: <witness> = complete_type_witness %.8 [template]
 // CHECK:STDOUT:   %Access.type: type = fn_type @Access [template]
 // CHECK:STDOUT:   %Access: %Access.type = struct_value () [template]
-// CHECK:STDOUT:   %.4: type = ptr_type %.3 [template]
+// CHECK:STDOUT:   %.10: type = struct_type {.base: %.5, .d: i32} [template]
+// CHECK:STDOUT:   %.11: type = ptr_type %.10 [template]
+// CHECK:STDOUT:   %.12: type = ptr_type %.8 [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
@@ -176,41 +686,74 @@ abstract class AbstractDecl;
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   package: <namespace> = namespace [template] {
 // CHECK:STDOUT:     .Core = imports.%Core
+// CHECK:STDOUT:     .Abstract = %Abstract.decl
+// CHECK:STDOUT:     .Derived = %Derived.decl
 // CHECK:STDOUT:     .Access = %Access.decl
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %Core.import = import Core
+// CHECK:STDOUT:   %Abstract.decl: type = class_decl @Abstract [template = constants.%Abstract] {} {}
+// CHECK:STDOUT:   %Derived.decl: type = class_decl @Derived [template = constants.%Derived] {} {}
 // CHECK:STDOUT:   %Access.decl: %Access.type = fn_decl @Access [template = constants.%Access] {
-// CHECK:STDOUT:     %d.patt: <error> = binding_pattern d
+// CHECK:STDOUT:     %d.patt: %Derived = binding_pattern d
 // CHECK:STDOUT:   } {
-// CHECK:STDOUT:     %Derived.ref: <error> = name_ref Derived, <error> [template = <error>]
-// CHECK:STDOUT:     %d.param: <error> = param d, runtime_param0
-// CHECK:STDOUT:     %d: <error> = bind_name d, %d.param
-// CHECK:STDOUT:     %int.make_type_32.loc8_27: init type = call constants.%Int32() [template = i32]
-// CHECK:STDOUT:     %int.make_type_32.loc8_32: init type = call constants.%Int32() [template = i32]
-// CHECK:STDOUT:     %.loc8_35.1: %.2 = tuple_literal (%int.make_type_32.loc8_27, %int.make_type_32.loc8_32)
-// CHECK:STDOUT:     %.loc8_35.2: type = value_of_initializer %int.make_type_32.loc8_27 [template = i32]
-// CHECK:STDOUT:     %.loc8_35.3: type = converted %int.make_type_32.loc8_27, %.loc8_35.2 [template = i32]
-// CHECK:STDOUT:     %.loc8_35.4: type = value_of_initializer %int.make_type_32.loc8_32 [template = i32]
-// CHECK:STDOUT:     %.loc8_35.5: type = converted %int.make_type_32.loc8_32, %.loc8_35.4 [template = i32]
-// CHECK:STDOUT:     %.loc8_35.6: type = converted %.loc8_35.1, constants.%.3 [template = constants.%.3]
-// CHECK:STDOUT:     %return: ref %.3 = var <return slot>
+// CHECK:STDOUT:     %Derived.ref: type = name_ref Derived, file.%Derived.decl [template = constants.%Derived]
+// CHECK:STDOUT:     %d.param: %Derived = param d, runtime_param0
+// CHECK:STDOUT:     %d: %Derived = bind_name d, %d.param
+// CHECK:STDOUT:     %int.make_type_32: init type = call constants.%Int32() [template = i32]
+// CHECK:STDOUT:     %.loc14_26.1: type = value_of_initializer %int.make_type_32 [template = i32]
+// CHECK:STDOUT:     %.loc14_26.2: type = converted %int.make_type_32, %.loc14_26.1 [template = i32]
+// CHECK:STDOUT:     %return: ref i32 = var <return slot>
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: class @Abstract {
+// CHECK:STDOUT:   %int.make_type_32: init type = call constants.%Int32() [template = i32]
+// CHECK:STDOUT:   %.loc5_10.1: type = value_of_initializer %int.make_type_32 [template = i32]
+// CHECK:STDOUT:   %.loc5_10.2: type = converted %int.make_type_32, %.loc5_10.1 [template = i32]
+// CHECK:STDOUT:   %.loc5_8: %.2 = field_decl a, element0 [template]
+// CHECK:STDOUT:   %.loc6: <witness> = complete_type_witness %.3 [template = constants.%.4]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%Abstract
+// CHECK:STDOUT:   .a = %.loc5_8
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Derived {
+// CHECK:STDOUT:   %Abstract.ref: type = name_ref Abstract, file.%Abstract.decl [template = constants.%Abstract]
+// CHECK:STDOUT:   %.loc9: %.6 = base_decl %Abstract, element0 [template]
+// CHECK:STDOUT:   %int.make_type_32: init type = call constants.%Int32() [template = i32]
+// CHECK:STDOUT:   %.loc11_10.1: type = value_of_initializer %int.make_type_32 [template = i32]
+// CHECK:STDOUT:   %.loc11_10.2: type = converted %int.make_type_32, %.loc11_10.1 [template = i32]
+// CHECK:STDOUT:   %.loc11_8: %.7 = field_decl d, element1 [template]
+// CHECK:STDOUT:   %.loc12: <witness> = complete_type_witness %.8 [template = constants.%.9]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%Derived
+// CHECK:STDOUT:   .base = %.loc9
+// CHECK:STDOUT:   .d = %.loc11_8
+// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Access(%d: <error>) -> %return: %.3 {
+// CHECK:STDOUT: fn @Access(%d: %Derived) -> i32 {
 // CHECK:STDOUT: !entry:
-// CHECK:STDOUT:   %d.ref.loc9_11: <error> = name_ref d, %d
-// CHECK:STDOUT:   %d.ref.loc9_16: <error> = name_ref d, %d
-// CHECK:STDOUT:   %.loc9: <error> = tuple_literal (<error>, <error>)
-// CHECK:STDOUT:   return <error> to %return
+// CHECK:STDOUT:   %d.ref: %Derived = name_ref d, %d
+// CHECK:STDOUT:   %base.ref: %.6 = name_ref base, @Derived.%.loc9 [template = @Derived.%.loc9]
+// CHECK:STDOUT:   %.loc15: ref %Abstract = class_element_access %d.ref, element0
+// CHECK:STDOUT:   return <error>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: --- fail_abstract_decl.carbon
+// CHECK:STDOUT: --- abstract_let_temporary.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
-// CHECK:STDOUT:   %AbstractDecl: type = class_type @AbstractDecl [template]
+// CHECK:STDOUT:   %Abstract: type = class_type @Abstract [template]
+// CHECK:STDOUT:   %.1: type = struct_type {} [template]
+// CHECK:STDOUT:   %.2: <witness> = complete_type_witness %.1 [template]
+// CHECK:STDOUT:   %F.type: type = fn_type @F [template]
+// CHECK:STDOUT:   %.3: type = tuple_type () [template]
+// CHECK:STDOUT:   %F: %F.type = struct_value () [template]
+// CHECK:STDOUT:   %.4: type = ptr_type %.1 [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
@@ -229,11 +772,85 @@ abstract class AbstractDecl;
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   package: <namespace> = namespace [template] {
 // CHECK:STDOUT:     .Core = imports.%Core
-// CHECK:STDOUT:     .AbstractDecl = %AbstractDecl.decl
+// CHECK:STDOUT:     .Abstract = %Abstract.decl
+// CHECK:STDOUT:     .F = %F.decl
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %Core.import = import Core
-// CHECK:STDOUT:   %AbstractDecl.decl: type = class_decl @AbstractDecl [template = constants.%AbstractDecl] {} {}
+// CHECK:STDOUT:   %Abstract.decl: type = class_decl @Abstract [template = constants.%Abstract] {} {}
+// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [template = constants.%F] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Abstract {
+// CHECK:STDOUT:   %.loc5: <witness> = complete_type_witness %.1 [template = constants.%.2]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%Abstract
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: class @AbstractDecl;
+// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Abstract.ref: type = name_ref Abstract, file.%Abstract.decl [template = constants.%Abstract]
+// CHECK:STDOUT:   %.loc8: %.1 = struct_literal ()
+// CHECK:STDOUT:   %l: %Abstract = bind_name l, <error>
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_call_abstract_return.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %Abstract: type = class_type @Abstract [template]
+// CHECK:STDOUT:   %.1: type = struct_type {} [template]
+// CHECK:STDOUT:   %.2: <witness> = complete_type_witness %.1 [template]
+// CHECK:STDOUT:   %ReturnAbstract.type: type = fn_type @ReturnAbstract [template]
+// CHECK:STDOUT:   %.3: type = tuple_type () [template]
+// CHECK:STDOUT:   %ReturnAbstract: %ReturnAbstract.type = struct_value () [template]
+// CHECK:STDOUT:   %CallReturnAbstract.type: type = fn_type @CallReturnAbstract [template]
+// CHECK:STDOUT:   %CallReturnAbstract: %CallReturnAbstract.type = struct_value () [template]
+// CHECK:STDOUT:   %.4: type = ptr_type %.1 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Core: <namespace> = namespace file.%Core.import, [template] {
+// CHECK:STDOUT:     import Core//prelude
+// CHECK:STDOUT:     import Core//prelude/operators
+// CHECK:STDOUT:     import Core//prelude/types
+// CHECK:STDOUT:     import Core//prelude/operators/arithmetic
+// CHECK:STDOUT:     import Core//prelude/operators/as
+// CHECK:STDOUT:     import Core//prelude/operators/bitwise
+// CHECK:STDOUT:     import Core//prelude/operators/comparison
+// CHECK:STDOUT:     import Core//prelude/types/bool
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = imports.%Core
+// CHECK:STDOUT:     .Abstract = %Abstract.decl
+// CHECK:STDOUT:     .ReturnAbstract = %ReturnAbstract.decl
+// CHECK:STDOUT:     .CallReturnAbstract = %CallReturnAbstract.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core.import = import Core
+// CHECK:STDOUT:   %Abstract.decl: type = class_decl @Abstract [template = constants.%Abstract] {} {}
+// CHECK:STDOUT:   %ReturnAbstract.decl: %ReturnAbstract.type = fn_decl @ReturnAbstract [template = constants.%ReturnAbstract] {} {
+// CHECK:STDOUT:     %Abstract.ref: type = name_ref Abstract, file.%Abstract.decl [template = constants.%Abstract]
+// CHECK:STDOUT:     %return: ref %Abstract = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %CallReturnAbstract.decl: %CallReturnAbstract.type = fn_decl @CallReturnAbstract [template = constants.%CallReturnAbstract] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Abstract {
+// CHECK:STDOUT:   %.loc5: <witness> = complete_type_witness %.1 [template = constants.%.2]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%Abstract
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @ReturnAbstract() -> %Abstract;
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @CallReturnAbstract() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %ReturnAbstract.ref: %ReturnAbstract.type = name_ref ReturnAbstract, file.%ReturnAbstract.decl [template = constants.%ReturnAbstract]
+// CHECK:STDOUT:   %ReturnAbstract.call: init <error> = call %ReturnAbstract.ref()
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 1 - 1
toolchain/check/testdata/class/fail_import_misuses.carbon

@@ -34,7 +34,7 @@ import library "a";
 class Empty {
 }
 
-// CHECK:STDERR: fail_b.carbon:[[@LINE+7]]:8: error: Variable has incomplete type `Incomplete`
+// CHECK:STDERR: fail_b.carbon:[[@LINE+7]]:8: error: variable has incomplete type `Incomplete`
 // CHECK:STDERR: var a: Incomplete;
 // CHECK:STDERR:        ^~~~~~~~~~
 // CHECK:STDERR: fail_b.carbon:[[@LINE-16]]:1: in import

+ 1 - 1
toolchain/check/testdata/class/fail_incomplete.carbon

@@ -30,7 +30,7 @@ fn CallClassFunction() {
   Class.Function();
 }
 
-// CHECK:STDERR: fail_incomplete.carbon:[[@LINE+7]]:17: error: Variable has incomplete type `Class`
+// CHECK:STDERR: fail_incomplete.carbon:[[@LINE+7]]:17: error: variable has incomplete type `Class`
 // CHECK:STDERR: var global_var: Class;
 // CHECK:STDERR:                 ^~~~~
 // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-25]]:1: note: class was forward declared here

+ 1 - 1
toolchain/check/testdata/struct/no_prelude/fail_nested_incomplete.carbon

@@ -10,7 +10,7 @@
 
 class Incomplete;
 
-// CHECK:STDERR: fail_nested_incomplete.carbon:[[@LINE+6]]:8: error: Variable has incomplete type `{.a: Incomplete}`
+// CHECK:STDERR: fail_nested_incomplete.carbon:[[@LINE+6]]:8: error: variable has incomplete type `{.a: Incomplete}`
 // CHECK:STDERR: var s: {.a: Incomplete};
 // CHECK:STDERR:        ^~~~~~~~~~~~~~~~
 // CHECK:STDERR: fail_nested_incomplete.carbon:[[@LINE-5]]:1: note: class was forward declared here

+ 1 - 1
toolchain/check/testdata/tuple/fail_nested_incomplete.carbon

@@ -10,7 +10,7 @@
 
 class Incomplete;
 
-// CHECK:STDERR: fail_nested_incomplete.carbon:[[@LINE+6]]:8: error: Variable has incomplete type `(i32, Incomplete)`
+// CHECK:STDERR: fail_nested_incomplete.carbon:[[@LINE+6]]:8: error: variable has incomplete type `(i32, Incomplete)`
 // CHECK:STDERR: var t: (i32, Incomplete);
 // CHECK:STDERR:        ^~~~~~~~~~~~~~~~~
 // CHECK:STDERR: fail_nested_incomplete.carbon:[[@LINE-5]]:1: note: class was forward declared here

+ 26 - 1
toolchain/diagnostics/diagnostic_emitter.h

@@ -63,6 +63,9 @@ class DiagnosticEmitter {
     auto Note(LocT loc,
               const Internal::DiagnosticBase<Args...>& diagnostic_base,
               Internal::NoTypeDeduction<Args>... args) -> DiagnosticBuilder& {
+      if (!emitter_) {
+        return *this;
+      }
       CARBON_CHECK(diagnostic_base.Level == DiagnosticLevel::Note ||
                        diagnostic_base.Level == DiagnosticLevel::LocationInfo,
                    "{0}", static_cast<int>(diagnostic_base.Level));
@@ -74,12 +77,20 @@ class DiagnosticEmitter {
     // For the expected usage see the builder API: `DiagnosticEmitter::Build`.
     template <typename... Args>
     auto Emit() -> void {
+      if (!emitter_) {
+        return;
+      }
       for (auto annotate_fn : emitter_->annotate_fns_) {
         annotate_fn(*this);
       }
       emitter_->consumer_->HandleDiagnostic(std::move(diagnostic_));
     }
 
+    // Returns true if this DiagnosticBuilder may emit a diagnostic. Can be used
+    // to avoid excess work computing notes, etc, if no diagnostic is going to
+    // be emitted anyway.
+    explicit operator bool() { return emitter_; }
+
    private:
     friend class DiagnosticEmitter<LocT>;
 
@@ -93,12 +104,19 @@ class DiagnosticEmitter {
       CARBON_CHECK(diagnostic_base.Level != DiagnosticLevel::Note);
     }
 
+    // Create a null `DiagnosticBuilder` that will not emit anything. Notes will
+    // be silently ignored.
+    DiagnosticBuilder() : emitter_(nullptr) {}
+
     // Adds a message to the diagnostic, handling conversion of the location and
     // arguments.
     template <typename... Args>
     auto AddMessage(LocT loc,
                     const Internal::DiagnosticBase<Args...>& diagnostic_base,
                     llvm::SmallVector<llvm::Any> args) -> void {
+      if (!emitter_) {
+        return;
+      }
       AddMessageWithDiagnosticLoc(
           emitter_->converter_->ConvertLoc(
               loc,
@@ -117,7 +135,10 @@ class DiagnosticEmitter {
     auto AddMessageWithDiagnosticLoc(
         DiagnosticLoc loc,
         const Internal::DiagnosticBase<Args...>& diagnostic_base,
-        llvm::SmallVector<llvm::Any> args) {
+        llvm::SmallVector<llvm::Any> args) -> void {
+      if (!emitter_) {
+        return;
+      }
       diagnostic_.messages.emplace_back(DiagnosticMessage{
           .kind = diagnostic_base.Kind,
           .level = diagnostic_base.Level,
@@ -183,6 +204,10 @@ class DiagnosticEmitter {
                              {MakeAny<Args>(args)...});
   }
 
+  // Create a null `DiagnosticBuilder` that will not emit anything. Notes will
+  // be silently ignored.
+  auto BuildSuppressed() -> DiagnosticBuilder { return DiagnosticBuilder(); }
+
  private:
   // Converts an argument to llvm::Any for storage, handling input to storage
   // type conversion when needed.

+ 5 - 1
toolchain/diagnostics/diagnostic_kind.def

@@ -217,11 +217,11 @@ CARBON_DIAGNOSTIC_KIND(AdaptWithVirtualHere)
 CARBON_DIAGNOSTIC_KIND(BaseDeclRepeated)
 CARBON_DIAGNOSTIC_KIND(BaseIsFinal)
 CARBON_DIAGNOSTIC_KIND(BaseMissingExtend)
+CARBON_DIAGNOSTIC_KIND(ClassAbstractHere)
 CARBON_DIAGNOSTIC_KIND(ClassForwardDeclaredHere)
 CARBON_DIAGNOSTIC_KIND(ClassSpecificDeclOutsideClass)
 CARBON_DIAGNOSTIC_KIND(ClassSpecificDeclPrevious)
 CARBON_DIAGNOSTIC_KIND(ClassIncompleteWithinDefinition)
-CARBON_DIAGNOSTIC_KIND(ConstructionOfAbstractClass)
 
 // Deduction.
 CARBON_DIAGNOSTIC_KIND(DeductionIncomplete)
@@ -274,6 +274,10 @@ CARBON_DIAGNOSTIC_KIND(NameAmbiguousDueToExtend)
 CARBON_DIAGNOSTIC_KIND(NameNotFound)
 CARBON_DIAGNOSTIC_KIND(NoPeriodSelfForDesignator)
 
+CARBON_DIAGNOSTIC_KIND(AbstractTypeInAdaptDecl)
+CARBON_DIAGNOSTIC_KIND(AbstractTypeInFunctionReturnType)
+CARBON_DIAGNOSTIC_KIND(AbstractTypeInInit)
+CARBON_DIAGNOSTIC_KIND(AbstractTypeInVarDecl)
 CARBON_DIAGNOSTIC_KIND(AddrOfEphemeralRef)
 CARBON_DIAGNOSTIC_KIND(AddrOfNonRef)
 CARBON_DIAGNOSTIC_KIND(AddrOnNonSelfParam)