Просмотр исходного кода

Diagnose missing definitions in impl files (#4079)

Geoff Romer 1 год назад
Родитель
Сommit
5a8dfda4f0

+ 46 - 0
toolchain/check/check.cpp

@@ -10,6 +10,7 @@
 #include "common/error.h"
 #include "common/variant_helpers.h"
 #include "common/vlog.h"
+#include "toolchain/base/kind_switch.h"
 #include "toolchain/base/pretty_stack_trace_function.h"
 #include "toolchain/check/context.h"
 #include "toolchain/check/diagnostic_helpers.h"
@@ -656,6 +657,49 @@ auto NodeIdTraversal::Next() -> std::optional<Parse::NodeId> {
   }
 }
 
+// Emits a diagnostic for each declaration in context.definitions_required()
+// that doesn't have a definition.
+static auto DiagnoseMissingDefinitions(Context& context,
+                                       Context::DiagnosticEmitter& emitter)
+    -> void {
+  CARBON_DIAGNOSTIC(MissingDefinitionInImpl, Error,
+                    "No definition found for declaration in impl file");
+  for (SemIR::InstId decl_inst_id : context.definitions_required()) {
+    SemIR::Inst decl_inst = context.insts().Get(decl_inst_id);
+    CARBON_KIND_SWITCH(context.insts().Get(decl_inst_id)) {
+      case CARBON_KIND(SemIR::ClassDecl class_decl): {
+        if (!context.classes().Get(class_decl.class_id).is_defined()) {
+          emitter.Emit(decl_inst_id, MissingDefinitionInImpl);
+        }
+        break;
+      }
+      case CARBON_KIND(SemIR::FunctionDecl function_decl): {
+        if (context.functions().Get(function_decl.function_id).definition_id ==
+            SemIR::InstId::Invalid) {
+          emitter.Emit(decl_inst_id, MissingDefinitionInImpl);
+        }
+        break;
+      }
+      case CARBON_KIND(SemIR::ImplDecl impl_decl): {
+        if (!context.impls().Get(impl_decl.impl_id).is_defined()) {
+          emitter.Emit(decl_inst_id, MissingDefinitionInImpl);
+        }
+        break;
+      }
+      case SemIR::InterfaceDecl::Kind: {
+        // TODO: handle `interface` as well, once we can test it without
+        // triggering https://github.com/carbon-language/carbon-lang/issues/4071
+        CARBON_FATAL()
+            << "TODO: Support interfaces in DiagnoseMissingDefinitions";
+      }
+      default: {
+        CARBON_FATAL() << "Unexpected inst in definitions_required: "
+                       << decl_inst;
+      }
+    }
+  }
+}
+
 // Loops over all nodes in the tree. On some errors, this may return early,
 // for example if an unrecoverable state is encountered.
 // NOLINTNEXTLINE(readability-function-size)
@@ -736,6 +780,8 @@ static auto CheckParseTree(
   context.FinalizeExports();
   context.FinalizeGlobalInit();
 
+  DiagnoseMissingDefinitions(context, emitter);
+
   context.VerifyOnFinish();
 
   sem_ir.set_has_errors(unit_info.err_tracker.seen_error());

+ 14 - 0
toolchain/check/context.h

@@ -311,6 +311,12 @@ class Context {
     return check_ir_map_[sem_ir.check_ir_id().index];
   }
 
+  // True if the current file is an impl file.
+  auto IsImplFile() -> bool {
+    return sem_ir_->import_irs().Get(SemIR::ImportIRId::ApiForImpl).sem_ir !=
+           nullptr;
+  }
+
   // Prints information for a stack dump.
   auto PrintForStackDump(llvm::raw_ostream& output) const -> void;
 
@@ -406,6 +412,10 @@ class Context {
   }
   auto constants() -> SemIR::ConstantStore& { return sem_ir().constants(); }
 
+  auto definitions_required() -> llvm::SmallVector<SemIR::InstId>& {
+    return definitions_required_;
+  }
+
  private:
   // A FoldingSet node for a type.
   class TypeNode : public llvm::FastFoldingSetNode {
@@ -481,6 +491,10 @@ class Context {
   //
   // Inline 0 elements because it's expected to require heap allocation.
   llvm::SmallVector<SemIR::ConstantValueStore, 0> import_ir_constant_values_;
+
+  // Declaration instructions of entities that should have definitions by the
+  // end of the current source file.
+  llvm::SmallVector<SemIR::InstId> definitions_required_;
 };
 
 }  // namespace Carbon::Check

+ 4 - 0
toolchain/check/handle_class.cpp

@@ -263,6 +263,10 @@ static auto BuildClassDecl(Context& context, Parse::AnyClassDeclId node_id,
     }
   }
 
+  if (!is_definition && context.IsImplFile() && !is_extern) {
+    context.definitions_required().push_back(class_decl_id);
+  }
+
   return {class_decl.class_id, class_decl_id};
 }
 

+ 4 - 0
toolchain/check/handle_function.cpp

@@ -303,6 +303,10 @@ static auto BuildFunctionDecl(Context& context,
     }
   }
 
+  if (!is_definition && context.IsImplFile() && !is_extern) {
+    context.definitions_required().push_back(function_info.decl_id);
+  }
+
   return {function_decl.function_id, function_info.decl_id};
 }
 

+ 9 - 3
toolchain/check/handle_impl.cpp

@@ -174,7 +174,8 @@ static auto ExtendImpl(Context& context, Parse::NodeId extend_node,
 
 // Build an ImplDecl describing the signature of an impl. This handles the
 // common logic shared by impl forward declarations and impl definitions.
-static auto BuildImplDecl(Context& context, Parse::AnyImplDeclId node_id)
+static auto BuildImplDecl(Context& context, Parse::AnyImplDeclId node_id,
+                          bool is_definition)
     -> std::pair<SemIR::ImplId, SemIR::InstId> {
   auto [constraint_node, constraint_id] =
       context.node_stack().PopExprWithNodeId();
@@ -221,18 +222,23 @@ static auto BuildImplDecl(Context& context, Parse::AnyImplDeclId node_id)
                params_node, constraint_type_id);
   }
 
+  if (!is_definition && context.IsImplFile()) {
+    context.definitions_required().push_back(impl_decl_id);
+  }
+
   return {impl_decl.impl_id, impl_decl_id};
 }
 
 auto HandleImplDecl(Context& context, Parse::ImplDeclId node_id) -> bool {
-  BuildImplDecl(context, node_id);
+  BuildImplDecl(context, node_id, /*is_definition=*/false);
   context.decl_name_stack().PopScope();
   return true;
 }
 
 auto HandleImplDefinitionStart(Context& context,
                                Parse::ImplDefinitionStartId node_id) -> bool {
-  auto [impl_id, impl_decl_id] = BuildImplDecl(context, node_id);
+  auto [impl_id, impl_decl_id] =
+      BuildImplDecl(context, node_id, /*is_definition=*/true);
   auto& impl_info = context.impls().Get(impl_id);
 
   if (impl_info.is_defined()) {

+ 200 - 0
toolchain/check/testdata/class/no_prelude/no_definition_in_impl_file.carbon

@@ -0,0 +1,200 @@
+// 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
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/class/no_prelude/no_definition_in_impl_file.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/class/no_prelude/no_definition_in_impl_file.carbon
+
+// --- decl_in_api_definition_in_impl.carbon
+
+library "decl_in_api_definition_in_impl";
+
+class A;
+
+// --- decl_in_api_definition_in_impl.impl.carbon
+
+impl library "decl_in_api_definition_in_impl";
+
+class A;
+
+class A {}
+
+// --- use_decl_in_api.carbon
+
+library "use_decl_in_api";
+
+// --- use_decl_in_api.impl.carbon
+
+impl library "use_decl_in_api";
+
+import library "decl_in_api_definition_in_impl";
+
+// --- decl_only_in_api.carbon
+
+library "decl_only_in_api";
+
+class B;
+
+// --- decl_only_in_api.impl.carbon
+
+impl library "decl_only_in_api";
+
+// --- decl_in_api_decl_in_impl.carbon
+
+library "decl_in_api_decl_in_impl";
+
+class C;
+
+// --- fail_decl_in_api_decl_in_impl.impl.carbon
+
+impl library "decl_in_api_decl_in_impl";
+
+// CHECK:STDERR: fail_decl_in_api_decl_in_impl.impl.carbon:[[@LINE+4]]:1: ERROR: No definition found for declaration in impl file
+// CHECK:STDERR: class C;
+// CHECK:STDERR: ^~~~~~~~
+// CHECK:STDERR:
+class C;
+
+// --- decl_only_in_impl.carbon
+
+library "decl_only_in_impl";
+
+// --- fail_decl_only_in_impl.impl.carbon
+
+impl library "decl_only_in_impl";
+
+// CHECK:STDERR: fail_decl_only_in_impl.impl.carbon:[[@LINE+3]]:1: ERROR: No definition found for declaration in impl file
+// CHECK:STDERR: class D;
+// CHECK:STDERR: ^~~~~~~~
+class D;
+
+// CHECK:STDOUT: --- decl_in_api_definition_in_impl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %A: type = class_type @A [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .A = %A.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %A.decl: type = class_decl @A [template = constants.%A] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @A;
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- decl_in_api_definition_in_impl.impl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %A: type = class_type @A [template]
+// CHECK:STDOUT:   %.1: type = struct_type {} [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .A = %A.decl.loc4
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref: type = import_ref ir0, inst+1, loaded [template = constants.%A]
+// CHECK:STDOUT:   %A.decl.loc4: type = class_decl @A [template = constants.%A] {}
+// CHECK:STDOUT:   %A.decl.loc6: type = class_decl @A [template = constants.%A] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @A {
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%A
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- use_decl_in_api.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- use_decl_in_api.impl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .A = %import_ref
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref = import_ref ir1, inst+1, unloaded
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- decl_only_in_api.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %B: type = class_type @B [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .B = %B.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %B.decl: type = class_decl @B [template = constants.%B] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @B;
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- decl_only_in_api.impl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .B = %import_ref
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref = import_ref ir0, inst+1, unloaded
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- decl_in_api_decl_in_impl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %C: type = class_type @C [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .C = %C.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %C.decl: type = class_decl @C [template = constants.%C] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C;
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_decl_in_api_decl_in_impl.impl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %C: type = class_type @C [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .C = %C.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref: type = import_ref ir0, inst+1, loaded [template = constants.%C]
+// CHECK:STDOUT:   %C.decl: type = class_decl @C [template = constants.%C] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C;
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- decl_only_in_impl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_decl_only_in_impl.impl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %D: type = class_type @D [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .D = %D.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %D.decl: type = class_decl @D [template = constants.%D] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @D;
+// CHECK:STDOUT:

+ 10 - 3
toolchain/check/testdata/function/declaration/no_prelude/implicit_import.carbon

@@ -18,7 +18,7 @@ fn A();
 
 impl library "basic";
 
-fn A();
+fn A() {}
 
 // --- extern_api.carbon
 
@@ -30,7 +30,7 @@ extern fn A();
 
 impl library "extern_api";
 
-// CHECK:STDERR: fail_extern_api.impl.carbon:[[@LINE+10]]:1: ERROR: Redeclarations of `fn A` in the same library must match use of `extern`.
+// CHECK:STDERR: fail_extern_api.impl.carbon:[[@LINE+14]]:1: ERROR: Redeclarations of `fn A` in the same library must match use of `extern`.
 // CHECK:STDERR: fn A();
 // CHECK:STDERR: ^~~~~~~
 // CHECK:STDERR: fail_extern_api.impl.carbon:[[@LINE-5]]:6: In import.
@@ -40,6 +40,10 @@ impl library "extern_api";
 // CHECK:STDERR: extern fn A();
 // CHECK:STDERR: ^~~~~~~~~~~~~~
 // CHECK:STDERR:
+// CHECK:STDERR: fail_extern_api.impl.carbon:[[@LINE+4]]:1: ERROR: No definition found for declaration in impl file
+// CHECK:STDERR: fn A();
+// CHECK:STDERR: ^~~~~~~
+// CHECK:STDERR:
 fn A();
 
 // --- extern_impl.carbon
@@ -96,7 +100,10 @@ extern fn A();
 // CHECK:STDOUT:   %A.decl: %A.type = fn_decl @A [template = constants.%A] {}
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @A();
+// CHECK:STDOUT: fn @A() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- extern_api.carbon
 // CHECK:STDOUT:

+ 211 - 0
toolchain/check/testdata/function/declaration/no_prelude/no_definition_in_impl_file.carbon

@@ -0,0 +1,211 @@
+// 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
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/function/declaration/no_prelude/no_definition_in_impl_file.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/function/declaration/no_prelude/no_definition_in_impl_file.carbon
+
+// --- decl_in_api_definition_in_impl.carbon
+
+library "decl_in_api_definition_in_impl";
+
+fn A();
+
+// --- decl_in_api_definition_in_impl.impl.carbon
+
+impl library "decl_in_api_definition_in_impl";
+
+fn A();
+
+fn A() {}
+
+// --- use_decl_in_api.carbon
+
+library "use_decl_in_api";
+
+// --- use_decl_in_api.impl.carbon
+
+impl library "use_decl_in_api";
+
+import library "decl_in_api_definition_in_impl";
+
+// --- decl_only_in_api.carbon
+
+library "decl_only_in_api";
+
+fn B();
+
+// --- decl_only_in_api.impl.carbon
+
+impl library "decl_only_in_api";
+
+// --- decl_in_api_decl_in_impl.carbon
+
+library "decl_in_api_decl_in_impl";
+
+fn C();
+
+// --- fail_decl_in_api_decl_in_impl.impl.carbon
+
+impl library "decl_in_api_decl_in_impl";
+
+// CHECK:STDERR: fail_decl_in_api_decl_in_impl.impl.carbon:[[@LINE+4]]:1: ERROR: No definition found for declaration in impl file
+// CHECK:STDERR: fn C();
+// CHECK:STDERR: ^~~~~~~
+// CHECK:STDERR:
+fn C();
+
+// --- decl_only_in_impl.carbon
+
+library "decl_only_in_impl";
+
+// --- fail_decl_only_in_impl.impl.carbon
+
+impl library "decl_only_in_impl";
+
+// CHECK:STDERR: fail_decl_only_in_impl.impl.carbon:[[@LINE+3]]:1: ERROR: No definition found for declaration in impl file
+// CHECK:STDERR: fn D();
+// CHECK:STDERR: ^~~~~~~
+fn D();
+
+// CHECK:STDOUT: --- decl_in_api_definition_in_impl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %A.type: type = fn_type @A [template]
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %A: %A.type = struct_value () [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .A = %A.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %A.decl: %A.type = fn_decl @A [template = constants.%A] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @A();
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- decl_in_api_definition_in_impl.impl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %A.type: type = fn_type @A [template]
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %A: %A.type = struct_value () [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .A = %A.decl.loc4
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref: %A.type = import_ref ir0, inst+1, loaded [template = constants.%A]
+// CHECK:STDOUT:   %A.decl.loc4: %A.type = fn_decl @A [template = constants.%A] {}
+// CHECK:STDOUT:   %A.decl.loc6: %A.type = fn_decl @A [template = constants.%A] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @A() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- use_decl_in_api.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- use_decl_in_api.impl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .A = %import_ref
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref = import_ref ir1, inst+1, unloaded
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- decl_only_in_api.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %B.type: type = fn_type @B [template]
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %B: %B.type = struct_value () [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .B = %B.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %B.decl: %B.type = fn_decl @B [template = constants.%B] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @B();
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- decl_only_in_api.impl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .B = %import_ref
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref = import_ref ir0, inst+1, unloaded
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- decl_in_api_decl_in_impl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %C.type: type = fn_type @C [template]
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %C: %C.type = struct_value () [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .C = %C.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %C.decl: %C.type = fn_decl @C [template = constants.%C] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @C();
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_decl_in_api_decl_in_impl.impl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %C.type: type = fn_type @C [template]
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %C: %C.type = struct_value () [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .C = %C.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref: %C.type = import_ref ir0, inst+1, loaded [template = constants.%C]
+// CHECK:STDOUT:   %C.decl: %C.type = fn_decl @C [template = constants.%C] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @C();
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- decl_only_in_impl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_decl_only_in_impl.impl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %D.type: type = fn_type @D [template]
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %D: %D.type = struct_value () [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .D = %D.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %D.decl: %D.type = fn_decl @D [template = constants.%D] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @D();
+// CHECK:STDOUT:

+ 330 - 0
toolchain/check/testdata/impl/no_prelude/no_definition_in_impl_file.carbon

@@ -0,0 +1,330 @@
+// 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
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/impl/no_prelude/no_definition_in_impl_file.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/impl/no_prelude/no_definition_in_impl_file.carbon
+
+// --- decl_in_api_definition_in_impl.carbon
+
+library "decl_in_api_definition_in_impl";
+
+interface A {};
+
+impl () as A;
+
+// --- decl_in_api_definition_in_impl.impl.carbon
+
+impl library "decl_in_api_definition_in_impl";
+
+impl () as A;
+
+impl () as A {}
+
+// --- use_decl_in_api.carbon
+
+library "use_decl_in_api";
+
+// --- use_decl_in_api.impl.carbon
+
+impl library "use_decl_in_api";
+
+import library "decl_in_api_definition_in_impl";
+
+// --- decl_only_in_api.carbon
+
+library "decl_only_in_api";
+
+interface B {};
+
+impl () as B;
+
+// --- decl_only_in_api.impl.carbon
+
+impl library "decl_only_in_api";
+
+// --- decl_in_api_decl_in_impl.carbon
+
+library "decl_in_api_decl_in_impl";
+
+interface C {};
+
+impl () as C;
+
+// --- fail_decl_in_api_decl_in_impl.impl.carbon
+
+impl library "decl_in_api_decl_in_impl";
+
+// CHECK:STDERR: fail_decl_in_api_decl_in_impl.impl.carbon:[[@LINE+4]]:1: ERROR: No definition found for declaration in impl file
+// CHECK:STDERR: impl () as C;
+// CHECK:STDERR: ^~~~~~~~~~~~~
+// CHECK:STDERR:
+impl () as C;
+
+// --- decl_only_in_impl.carbon
+
+library "decl_only_in_impl";
+
+// --- fail_decl_only_in_impl.impl.carbon
+
+impl library "decl_only_in_impl";
+
+interface D {};
+
+// CHECK:STDERR: fail_decl_only_in_impl.impl.carbon:[[@LINE+3]]:1: ERROR: No definition found for declaration in impl file
+// CHECK:STDERR: impl () as D;
+// CHECK:STDERR: ^~~~~~~~~~~~~
+impl () as D;
+
+// CHECK:STDOUT: --- decl_in_api_definition_in_impl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: type = interface_type @A [template]
+// CHECK:STDOUT:   %Self: %.1 = bind_symbolic_name Self 0 [symbolic]
+// CHECK:STDOUT:   %.2: type = tuple_type () [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .A = %A.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %A.decl: type = interface_decl @A [template = constants.%.1] {}
+// CHECK:STDOUT:   impl_decl @impl {
+// CHECK:STDOUT:     %.loc6_7.1: %.2 = tuple_literal ()
+// CHECK:STDOUT:     %.loc6_7.2: type = converted %.loc6_7.1, constants.%.2 [template = constants.%.2]
+// CHECK:STDOUT:     %A.ref: type = name_ref A, %A.decl [template = constants.%.1]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: interface @A {
+// CHECK:STDOUT:   %Self: %.1 = bind_symbolic_name Self 0 [symbolic = constants.%Self]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
+// CHECK:STDOUT:   witness = ()
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: impl @impl: %.2 as %.1;
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- decl_in_api_definition_in_impl.impl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %.2: type = interface_type @A [template]
+// CHECK:STDOUT:   %Self: %.2 = bind_symbolic_name Self 0 [symbolic]
+// CHECK:STDOUT:   %.3: <witness> = interface_witness () [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .A = %import_ref.1
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref.1: type = import_ref ir0, inst+1, loaded [template = constants.%.2]
+// CHECK:STDOUT:   %import_ref.2 = import_ref ir0, inst+3, unloaded
+// CHECK:STDOUT:   impl_decl @impl {
+// CHECK:STDOUT:     %.loc4_7.1: %.1 = tuple_literal ()
+// CHECK:STDOUT:     %.loc4_7.2: type = converted %.loc4_7.1, constants.%.1 [template = constants.%.1]
+// CHECK:STDOUT:     %A.ref.loc4: type = name_ref A, %import_ref.1 [template = constants.%.2]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   impl_decl @impl {
+// CHECK:STDOUT:     %.loc6_7.1: %.1 = tuple_literal ()
+// CHECK:STDOUT:     %.loc6_7.2: type = converted %.loc6_7.1, constants.%.1 [template = constants.%.1]
+// CHECK:STDOUT:     %A.ref.loc6: type = name_ref A, %import_ref.1 [template = constants.%.2]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: interface @A {
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = file.%import_ref.2
+// CHECK:STDOUT:   witness = ()
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: impl @impl: %.1 as %.2 {
+// CHECK:STDOUT:   %.1: <witness> = interface_witness () [template = constants.%.3]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   witness = %.1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- use_decl_in_api.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- use_decl_in_api.impl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %.2: type = interface_type @A [template]
+// CHECK:STDOUT:   %Self: %.2 = bind_symbolic_name Self 0 [symbolic]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .A = %import_ref.1
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref.1 = import_ref ir1, inst+1, unloaded
+// CHECK:STDOUT:   %import_ref.2 = import_ref ir1, inst+3, unloaded
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: interface @A {
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = file.%import_ref.2
+// CHECK:STDOUT:   witness = ()
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: impl @impl: %.1 as %.2;
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- decl_only_in_api.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: type = interface_type @B [template]
+// CHECK:STDOUT:   %Self: %.1 = bind_symbolic_name Self 0 [symbolic]
+// CHECK:STDOUT:   %.2: type = tuple_type () [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .B = %B.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %B.decl: type = interface_decl @B [template = constants.%.1] {}
+// CHECK:STDOUT:   impl_decl @impl {
+// CHECK:STDOUT:     %.loc6_7.1: %.2 = tuple_literal ()
+// CHECK:STDOUT:     %.loc6_7.2: type = converted %.loc6_7.1, constants.%.2 [template = constants.%.2]
+// CHECK:STDOUT:     %B.ref: type = name_ref B, %B.decl [template = constants.%.1]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: interface @B {
+// CHECK:STDOUT:   %Self: %.1 = bind_symbolic_name Self 0 [symbolic = constants.%Self]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
+// CHECK:STDOUT:   witness = ()
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: impl @impl: %.2 as %.1;
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- decl_only_in_api.impl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %.2: type = interface_type @B [template]
+// CHECK:STDOUT:   %Self: %.2 = bind_symbolic_name Self 0 [symbolic]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .B = %import_ref.1
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref.1 = import_ref ir0, inst+1, unloaded
+// CHECK:STDOUT:   %import_ref.2 = import_ref ir0, inst+3, unloaded
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: interface @B {
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = file.%import_ref.2
+// CHECK:STDOUT:   witness = ()
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: impl @impl: %.1 as %.2;
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- decl_in_api_decl_in_impl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: type = interface_type @C [template]
+// CHECK:STDOUT:   %Self: %.1 = bind_symbolic_name Self 0 [symbolic]
+// CHECK:STDOUT:   %.2: type = tuple_type () [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .C = %C.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %C.decl: type = interface_decl @C [template = constants.%.1] {}
+// CHECK:STDOUT:   impl_decl @impl {
+// CHECK:STDOUT:     %.loc6_7.1: %.2 = tuple_literal ()
+// CHECK:STDOUT:     %.loc6_7.2: type = converted %.loc6_7.1, constants.%.2 [template = constants.%.2]
+// CHECK:STDOUT:     %C.ref: type = name_ref C, %C.decl [template = constants.%.1]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: interface @C {
+// CHECK:STDOUT:   %Self: %.1 = bind_symbolic_name Self 0 [symbolic = constants.%Self]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
+// CHECK:STDOUT:   witness = ()
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: impl @impl: %.2 as %.1;
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_decl_in_api_decl_in_impl.impl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %.2: type = interface_type @C [template]
+// CHECK:STDOUT:   %Self: %.2 = bind_symbolic_name Self 0 [symbolic]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .C = %import_ref.1
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref.1: type = import_ref ir0, inst+1, loaded [template = constants.%.2]
+// CHECK:STDOUT:   %import_ref.2 = import_ref ir0, inst+3, unloaded
+// CHECK:STDOUT:   impl_decl @impl {
+// CHECK:STDOUT:     %.loc8_7.1: %.1 = tuple_literal ()
+// CHECK:STDOUT:     %.loc8_7.2: type = converted %.loc8_7.1, constants.%.1 [template = constants.%.1]
+// CHECK:STDOUT:     %C.ref: type = name_ref C, %import_ref.1 [template = constants.%.2]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: interface @C {
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = file.%import_ref.2
+// CHECK:STDOUT:   witness = ()
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: impl @impl: %.1 as %.2;
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- decl_only_in_impl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_decl_only_in_impl.impl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: type = interface_type @D [template]
+// CHECK:STDOUT:   %Self: %.1 = bind_symbolic_name Self 0 [symbolic]
+// CHECK:STDOUT:   %.2: type = tuple_type () [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .D = %D.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %D.decl: type = interface_decl @D [template = constants.%.1] {}
+// CHECK:STDOUT:   impl_decl @impl {
+// CHECK:STDOUT:     %.loc9_7.1: %.2 = tuple_literal ()
+// CHECK:STDOUT:     %.loc9_7.2: type = converted %.loc9_7.1, constants.%.2 [template = constants.%.2]
+// CHECK:STDOUT:     %D.ref: type = name_ref D, %D.decl [template = constants.%.1]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: interface @D {
+// CHECK:STDOUT:   %Self: %.1 = bind_symbolic_name Self 0 [symbolic = constants.%Self]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
+// CHECK:STDOUT:   witness = ()
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: impl @impl: %.2 as %.1;
+// CHECK:STDOUT:

+ 1 - 0
toolchain/diagnostics/diagnostic_kind.def

@@ -151,6 +151,7 @@ CARBON_DIAGNOSTIC_KIND(ExplicitImportApi)
 CARBON_DIAGNOSTIC_KIND(RepeatedImport)
 CARBON_DIAGNOSTIC_KIND(FirstImported)
 CARBON_DIAGNOSTIC_KIND(ExportFromImpl)
+CARBON_DIAGNOSTIC_KIND(MissingDefinitionInImpl)
 
 // Merge-related redeclaration checking.
 CARBON_DIAGNOSTIC_KIND(RedeclPrevDecl)