Przeglądaj źródła

Redeclaration support for `impl` declarations. (#3717)

Maintain a mapping from (self type, constraint) to `ImplId` on the
`Impl` value store so that we can perform redeclaration lookup.
Richard Smith 2 lat temu
rodzic
commit
eed33c3b8c

+ 1 - 1
toolchain/check/context.h

@@ -365,7 +365,7 @@ class Context {
   auto interfaces() -> ValueStore<SemIR::InterfaceId>& {
     return sem_ir().interfaces();
   }
-  auto impls() -> ValueStore<SemIR::ImplId>& { return sem_ir().impls(); }
+  auto impls() -> SemIR::ImplStore& { return sem_ir().impls(); }
   auto import_irs() -> ValueStore<SemIR::ImportIRId>& {
     return sem_ir().import_irs();
   }

+ 8 - 12
toolchain/check/handle_impl.cpp

@@ -153,21 +153,17 @@ static auto BuildImplDecl(Context& context, Parse::AnyImplDeclId parse_node)
   auto name_context = context.decl_name_stack().FinishImplName();
   CARBON_CHECK(name_context.state == DeclNameStack::NameContext::State::Empty);
 
-  // Add the impl declaration.
-  auto impl_decl = SemIR::ImplDecl{SemIR::ImplId::Invalid, decl_block_id};
-  auto impl_decl_id = context.AddPlaceholderInst({parse_node, impl_decl});
+  // TODO: Check for an orphan `impl`.
 
-  // TODO: Check whether this is a redeclaration.
+  // TODO: Check parameters. Store them on the `Impl` in some form.
   static_cast<void>(params_id);
 
-  // Create a new impl if this isn't a valid redeclaration.
-  if (!impl_decl.impl_id.is_valid()) {
-    impl_decl.impl_id = context.impls().Add(
-        {.self_id = self_type_id, .constraint_id = constraint_type_id});
-  }
-
-  // Write the impl ID into the ImplDecl.
-  context.ReplaceInstBeforeConstantUse(impl_decl_id, {parse_node, impl_decl});
+  // Add the impl declaration.
+  // TODO: Does lookup in an impl file need to look for a prior impl declaration
+  // in the api file?
+  auto impl_id = context.impls().LookupOrAdd(self_type_id, constraint_type_id);
+  auto impl_decl = SemIR::ImplDecl{impl_id, decl_block_id};
+  auto impl_decl_id = context.AddInst({parse_node, impl_decl});
 
   // For an `extend impl` declaration, mark the impl as extending this `impl`.
   if (!!(context.decl_state_stack().innermost().modifier_set &

+ 41 - 0
toolchain/check/testdata/impl/fail_redefinition.carbon

@@ -0,0 +1,41 @@
+// 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
+
+interface I {}
+
+impl i32 as I {}
+
+// CHECK:STDERR: fail_redefinition.carbon:[[@LINE+6]]:1: ERROR: Redefinition of `impl i32 as I`.
+// CHECK:STDERR: impl i32 as I {}
+// CHECK:STDERR: ^~~~~~~~~~~~~~~
+// CHECK:STDERR: fail_redefinition.carbon:[[@LINE-5]]:1: Previous definition was here.
+// CHECK:STDERR: impl i32 as I {}
+// CHECK:STDERR: ^~~~~~~~~~~~~~~
+impl i32 as I {}
+
+// CHECK:STDOUT: --- fail_redefinition.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: type = interface_type @I [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace {.I = %I.decl} [template]
+// CHECK:STDOUT:   %I.decl = interface_decl @I, ()
+// CHECK:STDOUT:   impl_decl @impl, (<unexpected instref inst+3>)
+// CHECK:STDOUT:   impl_decl @impl, (<unexpected instref inst+5>)
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: interface @I {
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: impl @impl: i32 as I {
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 15 - 8
toolchain/check/testdata/impl/todo_redefinition.carbon → toolchain/check/testdata/impl/redeclaration.carbon

@@ -6,22 +6,28 @@
 
 interface I {}
 
-impl i32 as I {}
+impl i32 as I;
+
+class X {
+  impl i32 as I;
+}
 
-// TODO: Reject this redefinition.
 impl i32 as I {}
 
-// CHECK:STDOUT: --- todo_redefinition.carbon
+// CHECK:STDOUT: --- redeclaration.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %.1: type = interface_type @I [template]
+// CHECK:STDOUT:   %X: type = class_type @X [template]
+// CHECK:STDOUT:   %.2: type = struct_type {} [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
-// CHECK:STDOUT:   package: <namespace> = namespace {.I = %I.decl} [template]
+// CHECK:STDOUT:   package: <namespace> = namespace {.I = %I.decl, .X = %X.decl} [template]
 // CHECK:STDOUT:   %I.decl = interface_decl @I, ()
-// CHECK:STDOUT:   impl_decl @impl.1, (<unexpected instref inst+3>)
-// CHECK:STDOUT:   impl_decl @impl.2, (<unexpected instref inst+5>)
+// CHECK:STDOUT:   impl_decl @impl, (<unexpected instref inst+3>)
+// CHECK:STDOUT:   %X.decl = class_decl @X, ()
+// CHECK:STDOUT:   impl_decl @impl, (<unexpected instref inst+10>)
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: interface @I {
@@ -29,12 +35,13 @@ impl i32 as I {}
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: impl @impl.1: i32 as I {
+// CHECK:STDOUT: impl @impl: i32 as I {
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: impl @impl.2: i32 as I {
+// CHECK:STDOUT: class @X {
+// CHECK:STDOUT:   impl_decl @impl, (<unexpected instref inst+7>)
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT: }

+ 1 - 0
toolchain/sem_ir/BUILD

@@ -69,6 +69,7 @@ cc_library(
     ],
     hdrs = [
         "file.h",
+        "impl.h",
         "value_stores.h",
     ],
     deps = [

+ 4 - 36
toolchain/sem_ir/file.h

@@ -13,6 +13,7 @@
 #include "toolchain/base/value_store.h"
 #include "toolchain/base/yaml.h"
 #include "toolchain/sem_ir/ids.h"
+#include "toolchain/sem_ir/impl.h"
 #include "toolchain/sem_ir/type_info.h"
 #include "toolchain/sem_ir/value_stores.h"
 
@@ -175,39 +176,6 @@ struct Interface : public Printable<Interface> {
   bool defined = false;
 };
 
-// An implementation of a constraint.
-struct Impl : public Printable<Impl> {
-  auto Print(llvm::raw_ostream& out) const -> void {
-    out << "{self: " << self_id << ", constraint: " << constraint_id << "}";
-  }
-
-  // Determines whether this impl has been fully defined. This is false until we
-  // reach the `}` of the impl definition.
-  auto is_defined() const -> bool { return defined; }
-
-  // The following members always have values, and do not change throughout the
-  // lifetime of the interface.
-
-  // TODO: Track the generic parameters for `impl forall`.
-  // The type for which the impl is implementing a constraint.
-  TypeId self_id;
-  // The constraint that the impl implements.
-  TypeId constraint_id;
-
-  // The following members are set at the `{` of the impl definition.
-
-  // The definition of the impl. This is an ImplDecl.
-  InstId definition_id = InstId::Invalid;
-  // The impl scope.
-  NameScopeId scope_id = NameScopeId::Invalid;
-  // The first block of the impl body.
-  // TODO: Handle control flow in the impl body, such as if-expressions.
-  InstBlockId body_block_id = InstBlockId::Invalid;
-
-  // The following members are set at the `}` of the impl definition.
-  bool defined = false;
-};
-
 // Provides semantic analysis on a Parse::Tree.
 class File : public Printable<File> {
  public:
@@ -302,8 +270,8 @@ class File : public Printable<File> {
   auto interfaces() const -> const ValueStore<InterfaceId>& {
     return interfaces_;
   }
-  auto impls() -> ValueStore<ImplId>& { return impls_; }
-  auto impls() const -> const ValueStore<ImplId>& { return impls_; }
+  auto impls() -> ImplStore& { return impls_; }
+  auto impls() const -> const ImplStore& { return impls_; }
   auto import_irs() -> ValueStore<ImportIRId>& { return import_irs_; }
   auto import_irs() const -> const ValueStore<ImportIRId>& {
     return import_irs_;
@@ -378,7 +346,7 @@ class File : public Printable<File> {
   ValueStore<InterfaceId> interfaces_;
 
   // Storage for impls.
-  ValueStore<ImplId> impls_;
+  ImplStore impls_;
 
   // Related IRs. There will always be at least one entry, the builtin IR (used
   // for references of builtins).

+ 3 - 0
toolchain/sem_ir/ids.h

@@ -487,5 +487,8 @@ struct llvm::DenseMapInfo<Carbon::SemIR::InstId>
 template <>
 struct llvm::DenseMapInfo<Carbon::SemIR::NameScopeId>
     : public Carbon::IndexMapInfo<Carbon::SemIR::NameScopeId> {};
+template <>
+struct llvm::DenseMapInfo<Carbon::SemIR::TypeId>
+    : public Carbon::IndexMapInfo<Carbon::SemIR::TypeId> {};
 
 #endif  // CARBON_TOOLCHAIN_SEM_IR_IDS_H_

+ 83 - 0
toolchain/sem_ir/impl.h

@@ -0,0 +1,83 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#ifndef CARBON_TOOLCHAIN_SEM_IR_IMPL_H_
+#define CARBON_TOOLCHAIN_SEM_IR_IMPL_H_
+
+#include "llvm/ADT/DenseMap.h"
+#include "toolchain/sem_ir/ids.h"
+
+namespace Carbon::SemIR {
+
+// An implementation of a constraint.
+struct Impl : public Printable<Impl> {
+  auto Print(llvm::raw_ostream& out) const -> void {
+    out << "{self: " << self_id << ", constraint: " << constraint_id << "}";
+  }
+
+  // Determines whether this impl has been fully defined. This is false until we
+  // reach the `}` of the impl definition.
+  auto is_defined() const -> bool { return defined; }
+
+  // The following members always have values, and do not change throughout the
+  // lifetime of the interface.
+
+  // TODO: Track the generic parameters for `impl forall`.
+  // The type for which the impl is implementing a constraint.
+  TypeId self_id;
+  // The constraint that the impl implements.
+  TypeId constraint_id;
+
+  // The following members are set at the `{` of the impl definition.
+
+  // The definition of the impl. This is an ImplDecl.
+  InstId definition_id = InstId::Invalid;
+  // The impl scope.
+  NameScopeId scope_id = NameScopeId::Invalid;
+  // The first block of the impl body.
+  // TODO: Handle control flow in the impl body, such as if-expressions.
+  InstBlockId body_block_id = InstBlockId::Invalid;
+
+  // The following members are set at the `}` of the impl definition.
+  bool defined = false;
+};
+
+// A collection of `Impl`s, which can be accessed by the self type and
+// constraint implemented.
+class ImplStore {
+ public:
+  // Looks up the impl with this self type and constraint, or creates a new
+  // `Impl` if none exists.
+  // TODO: Handle parameters.
+  auto LookupOrAdd(TypeId self_id, TypeId constraint_id) -> ImplId {
+    auto [it, added] =
+        lookup_.insert({{self_id, constraint_id}, ImplId::Invalid});
+    if (added) {
+      it->second =
+          values_.Add({.self_id = self_id, .constraint_id = constraint_id});
+    }
+    return it->second;
+  }
+
+  // Returns a mutable value for an ID.
+  auto Get(ImplId id) -> Impl& { return values_.Get(id); }
+
+  // Returns the value for an ID.
+  auto Get(ImplId id) const -> const Impl& { return values_.Get(id); }
+
+  auto OutputYaml() const -> Yaml::OutputMapping {
+    return values_.OutputYaml();
+  }
+
+  auto array_ref() const -> llvm::ArrayRef<Impl> { return values_.array_ref(); }
+  auto size() const -> size_t { return values_.size(); }
+
+ private:
+  ValueStore<ImplId> values_;
+  llvm::DenseMap<std::pair<TypeId, TypeId>, ImplId> lookup_;
+};
+
+}  // namespace Carbon::SemIR
+
+#endif  // CARBON_TOOLCHAIN_SEM_IR_IMPL_H_