Преглед изворни кода

Support for templated impl declarations (#2700)

The strategy that we use for now to support template instantiation is to check the impl declaration as if it were a generic, but to defer all checking of the impl definition until we see a use in which all template parameters have arguments. At that point, we clone the impl definition and type-check the whole thing, with constant values set on the template parameters corresponding to the given arguments.

No caching of template instantiations is performed yet; each time we form a reference to a template instantiation, we instantiate it afresh. We also don't implement the name lookup rule from #949 yet; lookups during template instantiation look only in the actual type and not in the constraint.

Depends on #2699
Richard Smith пре 3 година
родитељ
комит
e0c90767be

+ 11 - 6
common/error.h

@@ -36,7 +36,7 @@ class [[nodiscard]] Error {
       : location_(std::move(other.location_)),
         message_(std::move(other.message_)) {}
 
-  Error& operator=(Error&& other) noexcept {
+  auto operator=(Error&& other) noexcept -> Error& {
     location_ = std::move(other.location_);
     message_ = std::move(other.message_);
     return *this;
@@ -122,7 +122,7 @@ class [[nodiscard]] ErrorOr {
   }
 
  private:
-  // Either an error message or
+  // Either an error message or a value.
   std::variant<Error, T> val_;
 };
 
@@ -171,15 +171,20 @@ class ErrorBuilder {
 #define CARBON_MAKE_UNIQUE_NAME_IMPL(a, b, c) a##b##c
 #define CARBON_MAKE_UNIQUE_NAME(a, b, c) CARBON_MAKE_UNIQUE_NAME_IMPL(a, b, c)
 
+// Macro to prevent a top-level comma from being interpreted as a macro
+// argument separator.
+#define CARBON_PROTECT_COMMAS(...) __VA_ARGS__
+
 #define CARBON_RETURN_IF_ERROR_IMPL(unique_name, expr)                    \
   if (auto unique_name = (expr); /* NOLINT(bugprone-macro-parentheses) */ \
       !(unique_name).ok()) {                                              \
     return std::move(unique_name).error();                                \
   }
 
-#define CARBON_RETURN_IF_ERROR(expr) \
-  CARBON_RETURN_IF_ERROR_IMPL(       \
-      CARBON_MAKE_UNIQUE_NAME(_llvm_error_line, __LINE__, __COUNTER__), expr)
+#define CARBON_RETURN_IF_ERROR(expr)                                    \
+  CARBON_RETURN_IF_ERROR_IMPL(                                          \
+      CARBON_MAKE_UNIQUE_NAME(_llvm_error_line, __LINE__, __COUNTER__), \
+      CARBON_PROTECT_COMMAS(expr))
 
 #define CARBON_ASSIGN_OR_RETURN_IMPL(unique_name, var, expr)          \
   auto unique_name = (expr); /* NOLINT(bugprone-macro-parentheses) */ \
@@ -191,6 +196,6 @@ class ErrorBuilder {
 #define CARBON_ASSIGN_OR_RETURN(var, expr)                                 \
   CARBON_ASSIGN_OR_RETURN_IMPL(                                            \
       CARBON_MAKE_UNIQUE_NAME(_llvm_expected_line, __LINE__, __COUNTER__), \
-      var, expr)
+      CARBON_PROTECT_COMMAS(var), CARBON_PROTECT_COMMAS(expr))
 
 #endif  // CARBON_COMMON_ERROR_H_

+ 5 - 0
common/fuzzing/carbon.proto

@@ -198,8 +198,13 @@ message BindingPattern {
 }
 
 message GenericBinding {
+  enum Kind {
+    Checked = 0;
+    Template = 1;
+  }
   optional string name = 1;
   optional Expression type = 2;
+  optional Kind kind = 3;
 }
 
 message TuplePattern {

+ 7 - 0
common/fuzzing/proto_to_carbon.cpp

@@ -433,6 +433,13 @@ static auto BindingPatternToCarbon(const Fuzzing::BindingPattern& pattern,
 
 static auto GenericBindingToCarbon(
     const Fuzzing::GenericBinding& generic_binding, llvm::raw_ostream& out) {
+  switch (generic_binding.kind()) {
+    case Fuzzing::GenericBinding::Checked:
+      break;
+    case Fuzzing::GenericBinding::Template:
+      out << "template ";
+      break;
+  }
   IdentifierToCarbon(generic_binding.name(), out);
   out << ":! ";
   ExpressionToCarbon(generic_binding.type(), out);

+ 2 - 1
explorer/ast/declaration.h

@@ -674,7 +674,8 @@ class ConstraintTypeDeclaration : public Declaration {
     auto* self_type_ref = arena->New<IdentifierExpression>(
         source_loc, std::string(name_.inner_name()));
     self_type_ref->set_value_node(self_type_);
-    self_ = arena->New<GenericBinding>(source_loc, "Self", self_type_ref);
+    self_ = arena->New<GenericBinding>(source_loc, "Self", self_type_ref,
+                                       GenericBinding::BindingKind::Checked);
   }
 
   explicit ConstraintTypeDeclaration(CloneContext& context,

+ 12 - 0
explorer/ast/pattern.cpp

@@ -33,7 +33,17 @@ void Pattern::Print(llvm::raw_ostream& out) const {
     }
     case PatternKind::GenericBinding: {
       const auto& binding = cast<GenericBinding>(*this);
+      switch (binding.binding_kind()) {
+        case GenericBinding::BindingKind::Checked:
+          break;
+        case GenericBinding::BindingKind::Template:
+          out << "template ";
+          break;
+      }
       out << binding.name() << ":! " << binding.type();
+      if (auto value = binding.constant_value()) {
+        out << " = " << **value;
+      }
       break;
     }
     case PatternKind::TuplePattern: {
@@ -177,6 +187,8 @@ GenericBinding::GenericBinding(CloneContext& context,
     : Pattern(context, other),
       name_(other.name_),
       type_(context.Clone(other.type_)),
+      binding_kind_(other.binding_kind_),
+      template_value_(context.Clone(other.template_value_)),
       symbolic_identity_(context.Clone(other.symbolic_identity_)),
       impl_binding_(context.Clone(other.impl_binding_)),
       original_(context.Remap(other.original_)),

+ 24 - 4
explorer/ast/pattern.h

@@ -270,11 +270,19 @@ class GenericBinding : public Pattern {
  public:
   using ImplementsCarbonValueNode = void;
 
-  GenericBinding(SourceLocation source_loc, std::string name,
-                 Nonnull<Expression*> type)
+  enum class BindingKind {
+    // A checked generic binding, `T:! type`.
+    Checked,
+    // A template generic binding, `template T:! type`.
+    Template,
+  };
+
+  explicit GenericBinding(SourceLocation source_loc, std::string name,
+                          Nonnull<Expression*> type, BindingKind binding_kind)
       : Pattern(AstNodeKind::GenericBinding, source_loc),
         name_(std::move(name)),
-        type_(type) {}
+        type_(type),
+        binding_kind_(binding_kind) {}
 
   explicit GenericBinding(CloneContext& context, const GenericBinding& other);
 
@@ -285,6 +293,7 @@ class GenericBinding : public Pattern {
   auto name() const -> const std::string& { return name_; }
   auto type() const -> const Expression& { return *type_; }
   auto type() -> Expression& { return *type_; }
+  auto binding_kind() const -> BindingKind { return binding_kind_; }
 
   // The index of this binding, which is the number of bindings that are in
   // scope at the point where this binding is declared.
@@ -299,7 +308,7 @@ class GenericBinding : public Pattern {
   auto value_category() const -> ValueCategory { return ValueCategory::Let; }
 
   auto constant_value() const -> std::optional<Nonnull<const Value*>> {
-    return std::nullopt;
+    return template_value_;
   }
 
   auto symbolic_identity() const -> std::optional<Nonnull<const Value*>> {
@@ -310,6 +319,15 @@ class GenericBinding : public Pattern {
     symbolic_identity_ = value;
   }
 
+  void set_template_value(Nonnull<const Value*> template_value) {
+    CARBON_CHECK(binding_kind_ == BindingKind::Template);
+    template_value_ = template_value;
+  }
+  auto has_template_value() const -> bool {
+    CARBON_CHECK(binding_kind_ == BindingKind::Template);
+    return template_value_.has_value();
+  }
+
   // The impl binding associated with this type variable.
   auto impl_binding() const -> std::optional<Nonnull<const ImplBinding*>> {
     return impl_binding_;
@@ -343,7 +361,9 @@ class GenericBinding : public Pattern {
  private:
   std::string name_;
   Nonnull<Expression*> type_;
+  BindingKind binding_kind_;
   std::optional<int> index_;
+  std::optional<Nonnull<const Value*>> template_value_;
   std::optional<Nonnull<const Value*>> symbolic_identity_;
   std::optional<Nonnull<const ImplBinding*>> impl_binding_;
   std::optional<Nonnull<const GenericBinding*>> original_;

+ 1 - 1
explorer/data/prelude.carbon

@@ -34,7 +34,7 @@ __match_first {
   // Pick up implicit conversions that are built into the compiler.
   // TODO: Split these into individual categories and implement as many as we can
   // in the prelude.
-  impl forall [U:! type, T:! __intrinsic_implicit_as(U)] T as ImplicitAs(U) {
+  impl forall [U:! type, template T:! __intrinsic_implicit_as(U)] T as ImplicitAs(U) {
     fn Convert[self: Self]() -> U { return __intrinsic_implicit_as_convert(self, U); }
   }
 

+ 0 - 22
explorer/fuzzing/BUILD

@@ -116,28 +116,6 @@ cc_test(
     ],
 )
 
-cc_test(
-    name = "clone_test",
-    srcs = ["clone_test.cpp"],
-    args = [
-        "$(locations //explorer:standard_libraries)",
-        "$(locations //explorer/testdata:carbon_files)",
-    ],
-    data = [
-        "//explorer:standard_libraries",
-        "//explorer/testdata:carbon_files",
-    ],
-    deps = [
-        ":ast_to_proto_lib",
-        "//common/fuzzing:carbon_cc_proto",
-        "//explorer/ast",
-        "//explorer/syntax",
-        "@com_google_googletest//:gtest",
-        "@com_google_protobuf//:protobuf_headers",
-        "@llvm-project//llvm:Support",
-    ],
-)
-
 cc_fuzz_test(
     name = "explorer_fuzzer",
     testonly = 1,

+ 8 - 0
explorer/fuzzing/ast_to_proto.cpp

@@ -379,6 +379,14 @@ static auto GenericBindingToProto(const GenericBinding& binding)
   Fuzzing::GenericBinding binding_proto;
   binding_proto.set_name(binding.name());
   *binding_proto.mutable_type() = ExpressionToProto(binding.type());
+  switch (binding.binding_kind()) {
+    case GenericBinding::BindingKind::Checked:
+      binding_proto.set_kind(Fuzzing::GenericBinding::Checked);
+      break;
+    case GenericBinding::BindingKind::Template:
+      binding_proto.set_kind(Fuzzing::GenericBinding::Template);
+      break;
+  }
   return binding_proto;
 }
 

+ 2 - 1
explorer/interpreter/interpreter.cpp

@@ -1506,7 +1506,8 @@ auto Interpreter::StepExp() -> ErrorOr<Success> {
           // it from here.
           auto* self_binding = arena_->New<GenericBinding>(
               exp.source_loc(), ".Self",
-              arena_->New<TypeTypeLiteral>(exp.source_loc()));
+              arena_->New<TypeTypeLiteral>(exp.source_loc()),
+              GenericBinding::BindingKind::Checked);
           auto* self = arena_->New<VariableType>(self_binding);
           auto* impl_binding = arena_->New<ImplBinding>(
               exp.source_loc(), self_binding, std::nullopt);

+ 193 - 23
explorer/interpreter/type_checker.cpp

@@ -392,6 +392,56 @@ static auto IsConcreteType(Nonnull<const Value*> value) -> bool {
   return IsType(value) && !TypeContainsAuto(value);
 }
 
+// Returns whether the given value is template-dependent, that is, if it
+// depends on any template paramaeter.
+static auto IsTemplateDependent(Nonnull<const Value*> value) -> bool {
+  // A VariableType is template dependent if it names a template binding.
+  if (auto* var_type = dyn_cast<VariableType>(value)) {
+    return var_type->binding().binding_kind() ==
+           GenericBinding::BindingKind::Template;
+  }
+
+  static constexpr auto is_dependent_value = [](auto&& x) -> bool {
+    if constexpr (std::is_convertible_v<decltype(x), const Value*>) {
+      return IsTemplateDependent(x);
+    }
+    return false;
+  };
+
+  // Any other value is template dependent if any part of it is.
+  return value->Visit<bool>([](auto* derived_value) {
+    return derived_value->Decompose([](auto&&... parts) {
+      return (is_dependent_value(decltype(parts)(parts)) || ...);
+    });
+  });
+}
+
+// Returns whether all template parameters in `bindings` are saturated: that
+// is, they have arguments that are not dependent on any template parameter.
+// This indicates that we're ready to perform template instantiation.
+static auto IsTemplateSaturated(const Bindings& bindings) -> bool {
+  for (auto [binding, value] : bindings.args()) {
+    if (binding->binding_kind() == GenericBinding::BindingKind::Template &&
+        IsTemplateDependent(value)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+// Returns whether all template parameters in `params` are saturated: that they
+// have template argument values specified.
+static auto IsTemplateSaturated(
+    llvm::ArrayRef<Nonnull<const GenericBinding*>> bindings) -> bool {
+  for (auto* binding : bindings) {
+    if (binding->binding_kind() == GenericBinding::BindingKind::Template &&
+        !binding->has_template_value()) {
+      return false;
+    }
+  }
+  return true;
+}
+
 // Returns the named field, or None if not found.
 static auto FindField(llvm::ArrayRef<NamedValue> fields,
                       const std::string& field_name)
@@ -1724,7 +1774,8 @@ class TypeChecker::ConstraintTypeBuilder {
     // Note, the type-of-type here is a placeholder and isn't really
     // meaningful.
     auto* result = arena->New<GenericBinding>(
-        source_loc, ".Self", arena->New<TypeTypeLiteral>(source_loc));
+        source_loc, ".Self", arena->New<TypeTypeLiteral>(source_loc),
+        GenericBinding::BindingKind::Checked);
     PrepareSelfBinding(arena, result);
     return result;
   }
@@ -1945,7 +1996,8 @@ class TypeChecker::SubstitutedGenericBindings {
     Nonnull<GenericBinding*> new_binding =
         type_checker_->arena_->New<GenericBinding>(
             old_binding->source_loc(), old_binding->name(),
-            const_cast<Expression*>(&old_binding->type()));
+            const_cast<Expression*>(&old_binding->type()),
+            old_binding->binding_kind());
     new_binding->set_original(old_binding->original());
     new_binding->set_static_type(new_type);
     bindings_.Add(old_binding,
@@ -2042,6 +2094,24 @@ class TypeChecker::SubstituteTransform
     }
   }
 
+  // When substituting into the bindings of an `ImplWitness`, we may need to
+  // perform template instantiation.
+  auto operator()(Nonnull<const ImplWitness*> witness)
+      -> ErrorOr<Nonnull<const ImplWitness*>> {
+    CARBON_ASSIGN_OR_RETURN(const auto* bindings,
+                            Transform(&witness->bindings()));
+    const auto* declaration = &witness->declaration();
+    if (!IsTemplateSaturated(witness->bindings()) &&
+        IsTemplateSaturated(*bindings)) {
+      CARBON_ASSIGN_OR_RETURN(
+          CARBON_PROTECT_COMMAS(auto [new_decl, new_bindings]),
+          type_checker_->InstantiateImplDeclaration(declaration, bindings));
+      declaration = new_decl;
+      bindings = new_bindings;
+    }
+    return type_checker_->arena_->New<ImplWitness>(declaration, bindings);
+  }
+
   // For an associated constant, look for a rewrite.
   auto operator()(Nonnull<const AssociatedConstant*> assoc)
       -> ErrorOr<Nonnull<const Value*>> {
@@ -5505,11 +5575,23 @@ auto TypeChecker::CheckAndAddImplBindings(
 }
 
 auto TypeChecker::DeclareImplDeclaration(Nonnull<ImplDeclaration*> impl_decl,
-                                         const ScopeInfo& scope_info)
+                                         const ScopeInfo& scope_info,
+                                         bool is_template_instantiation)
     -> ErrorOr<Success> {
   if (trace_stream_->is_enabled()) {
     *trace_stream_ << "declaring " << *impl_decl << "\n";
   }
+
+  if (!IsTemplateSaturated(impl_decl->deduced_parameters())) {
+    CloneContext context(arena_);
+    TemplateInfo template_info = {.pattern = context.Clone(impl_decl)};
+    for (auto deduced : impl_decl->deduced_parameters()) {
+      template_info.param_map.insert(
+          {deduced, context.GetExistingClone(deduced)});
+    }
+    templates_.insert({impl_decl, std::move(template_info)});
+  }
+
   ImplScope impl_scope(scope_info.innermost_scope);
   std::vector<Nonnull<const GenericBinding*>> generic_bindings =
       scope_info.bindings;
@@ -5549,8 +5631,9 @@ auto TypeChecker::DeclareImplDeclaration(Nonnull<ImplDeclaration*> impl_decl,
   Nonnull<const ConstraintType*> constraint_type;
   {
     // TODO: Combine this with the SelfDeclaration.
-    auto* self_binding = arena_->New<GenericBinding>(self->source_loc(), "Self",
-                                                     impl_decl->impl_type());
+    auto* self_binding = arena_->New<GenericBinding>(
+        self->source_loc(), "Self", impl_decl->impl_type(),
+        GenericBinding::BindingKind::Checked);
     self_binding->set_symbolic_identity(impl_type_value);
     self_binding->set_value(impl_type_value);
     auto* impl_binding = arena_->New<ImplBinding>(self_binding->source_loc(),
@@ -5578,14 +5661,24 @@ auto TypeChecker::DeclareImplDeclaration(Nonnull<ImplDeclaration*> impl_decl,
     impl_decl->set_constraint_type(constraint_type);
   }
 
+  // Declare the impl members. An `impl` behaves like a class scope.
+  ScopeInfo impl_scope_info =
+      ScopeInfo::ForClassScope(scope_info, &impl_scope, generic_bindings);
+  for (Nonnull<Declaration*> m : impl_decl->members()) {
+    CARBON_RETURN_IF_ERROR(DeclareDeclaration(m, impl_scope_info));
+  }
+
   // Build the self witness. This is the witness used to demonstrate that
   // this impl implements its lookup contexts.
   auto* self_witness = arena_->New<ImplWitness>(
       impl_decl, Bindings::SymbolicIdentity(arena_, generic_bindings));
 
-  // Compute a witness that the impl implements its constraint.
-  Nonnull<const Witness*> impl_witness;
-  {
+  // Check that this impl satisfies its constraints and push it into the
+  // ImplScope. For a templated impl, only the template is pushed into scope.
+  // Instantiations are found by substituting arguments into the parameterized
+  // ImplWitness.
+  if (!is_template_instantiation) {
+    // Compute a witness that the impl implements its constraint.
     std::vector<EqualityConstraint> rewrite_constraints_as_equality_constraints;
     ImplScope self_impl_scope(&impl_scope);
 
@@ -5609,22 +5702,16 @@ auto TypeChecker::DeclareImplDeclaration(Nonnull<ImplDeclaration*> impl_decl,
 
     // Ensure that's enough for our interface to be satisfied.
     CARBON_ASSIGN_OR_RETURN(
-        impl_witness, self_impl_scope.Resolve(constraint_type, impl_type_value,
-                                              impl_decl->source_loc(), *this));
-  }
+        Nonnull<const Witness*> impl_witness,
+        self_impl_scope.Resolve(constraint_type, impl_type_value,
+                                impl_decl->source_loc(), *this));
 
-  // Declare the impl members. An `impl` behaves like a class scope.
-  ScopeInfo impl_scope_info =
-      ScopeInfo::ForClassScope(scope_info, &impl_scope, generic_bindings);
-  for (Nonnull<Declaration*> m : impl_decl->members()) {
-    CARBON_RETURN_IF_ERROR(DeclareDeclaration(m, impl_scope_info));
+    // Create the implied impl bindings.
+    CARBON_RETURN_IF_ERROR(CheckAndAddImplBindings(
+        impl_decl, impl_type_value, self_witness, impl_witness,
+        generic_bindings, impl_scope_info));
   }
 
-  // Create the implied impl bindings.
-  CARBON_RETURN_IF_ERROR(
-      CheckAndAddImplBindings(impl_decl, impl_type_value, self_witness,
-                              impl_witness, generic_bindings, impl_scope_info));
-
   if (trace_stream_->is_enabled()) {
     *trace_stream_ << "** finished declaring impl " << *impl_decl->impl_type()
                    << " as " << impl_decl->interface() << "\n";
@@ -5663,6 +5750,13 @@ void TypeChecker::BringAssociatedConstantsIntoScope(
 auto TypeChecker::TypeCheckImplDeclaration(Nonnull<ImplDeclaration*> impl_decl,
                                            const ImplScope& enclosing_scope)
     -> ErrorOr<Success> {
+  if (!IsTemplateSaturated(impl_decl->deduced_parameters())) {
+    if (trace_stream_->is_enabled()) {
+      *trace_stream_ << "deferring checking templated " << *impl_decl << "\n";
+    }
+    return Success();
+  }
+
   if (trace_stream_->is_enabled()) {
     *trace_stream_ << "checking " << *impl_decl << "\n";
   }
@@ -5958,12 +6052,14 @@ auto TypeChecker::DeclareDeclaration(Nonnull<Declaration*> d,
     }
     case DeclarationKind::ImplDeclaration: {
       auto& impl_decl = cast<ImplDeclaration>(*d);
-      CARBON_RETURN_IF_ERROR(DeclareImplDeclaration(&impl_decl, scope_info));
+      CARBON_RETURN_IF_ERROR(DeclareImplDeclaration(
+          &impl_decl, scope_info, /*is_template_instantiation=*/false));
       break;
     }
     case DeclarationKind::MatchFirstDeclaration: {
       for (auto* impl : cast<MatchFirstDeclaration>(d)->impls()) {
-        CARBON_RETURN_IF_ERROR(DeclareImplDeclaration(impl, scope_info));
+        CARBON_RETURN_IF_ERROR(DeclareImplDeclaration(
+            impl, scope_info, /*is_template_instantiation=*/false));
       }
       break;
     }
@@ -6151,4 +6247,78 @@ auto TypeChecker::FindCollectedMembers(Nonnull<const Declaration*> decl)
   }
 }
 
+auto TypeChecker::InstantiateImplDeclaration(
+    Nonnull<const ImplDeclaration*> old_impl,
+    Nonnull<const Bindings*> bindings) const
+    -> ErrorOr<std::pair<Nonnull<ImplDeclaration*>, Nonnull<Bindings*>>> {
+  CARBON_CHECK(IsTemplateSaturated(*bindings));
+
+  if (trace_stream_->is_enabled()) {
+    *trace_stream_ << "instantiating " << *old_impl;
+  }
+
+  auto it = templates_.find(old_impl);
+  CARBON_CHECK(it != templates_.end());
+  const TemplateInfo& info = it->second;
+
+  // TODO: Only instantiate each declaration once for each set of template
+  // arguments.
+  CloneContext context(arena_);
+  Nonnull<ImplDeclaration*> impl =
+      context.Clone(cast<ImplDeclaration>(info.pattern));
+
+  // Update the binding to store its instantiated value or a link back to the
+  // original generic parameter.
+  Bindings new_bindings;
+  for (auto [param, value] : bindings->args()) {
+    auto param_it = info.param_map.find(param);
+    CARBON_CHECK(param_it != info.param_map.end());
+
+    auto* clone = context.GetExistingClone(param_it->second);
+    switch (param->binding_kind()) {
+      case GenericBinding::BindingKind::Template: {
+        clone->set_template_value(value);
+        // TODO: Set a constant value on the impl binding too, if there is one.
+        break;
+      }
+
+      case GenericBinding::BindingKind::Checked: {
+        std::optional<Nonnull<const Value*>> witness;
+        if (auto impl = param->impl_binding()) {
+          auto it = bindings->witnesses().find(*impl);
+          CARBON_CHECK(it != bindings->witnesses().end())
+              << "no witness for generic binding";
+          witness = it->second;
+        }
+        new_bindings.Add(clone, value, witness);
+        break;
+      }
+    }
+  }
+
+  // TODO: It's probably not correct to use the top-level impl scope here. It's
+  // not obvious what we should use, though -- which impls are in scope in
+  // template instantiation?
+  CARBON_CHECK(top_level_impl_scope_)
+      << "can't perform template instantiation with no top-level scope";
+  ImplScope scope(*top_level_impl_scope_);
+
+  // TODO: Remove the const-cast here. The requirement to perform template
+  // instantiation unfortunately means that a lot of type-checking stops being
+  // free of side-effects, so this means removing `const` throughout most of
+  // the type-checker.
+  auto* type_checker = const_cast<TypeChecker*>(this);
+
+  // Type-check the new impl.
+  //
+  // TODO: Augment any error we see here with an "instantiation failed" note
+  // pointing to the location where the instantiation was required.
+  CARBON_RETURN_IF_ERROR(type_checker->DeclareImplDeclaration(
+      impl, ScopeInfo::ForNonClassScope(&scope),
+      /*is_template_instantiation=*/true));
+  CARBON_RETURN_IF_ERROR(type_checker->TypeCheckImplDeclaration(impl, scope));
+
+  return std::pair{impl, arena_->New<Bindings>(std::move(new_bindings))};
+}
+
 }  // namespace Carbon

+ 24 - 1
explorer/interpreter/type_checker.h

@@ -280,7 +280,9 @@ class TypeChecker {
       const ScopeInfo& scope_info) -> ErrorOr<Success>;
 
   auto DeclareImplDeclaration(Nonnull<ImplDeclaration*> impl_decl,
-                              const ScopeInfo& scope_info) -> ErrorOr<Success>;
+                              const ScopeInfo& scope_info,
+                              bool is_template_instantiation)
+      -> ErrorOr<Success>;
 
   auto DeclareChoiceDeclaration(Nonnull<ChoiceDeclaration*> choice,
                                 const ScopeInfo& scope_info)
@@ -519,6 +521,12 @@ class TypeChecker {
   auto FindCollectedMembers(Nonnull<const Declaration*> decl)
       -> CollectedMembersMap&;
 
+  // Instantiate an impl with the given set of bindings, including one or more
+  // template bindings.
+  ErrorOr<std::pair<Nonnull<ImplDeclaration*>, Nonnull<Bindings*>>>
+  InstantiateImplDeclaration(Nonnull<const ImplDeclaration*> pattern,
+                             Nonnull<const Bindings*> bindings) const;
+
   Nonnull<Arena*> arena_;
   Builtins builtins_;
 
@@ -541,6 +549,21 @@ class TypeChecker {
   // the `const`s from everywhere that transitively does `impl` matching to get
   // rid of this `mutable`.
   mutable MatchingImplSet matching_impl_set_;
+
+  // Information about a generic that has one or more template parameters.
+  struct TemplateInfo {
+    // The original pattern, prior to any type-checking.
+    Nonnull<const Declaration*> pattern;
+    // A mapping from the bindings of the type-checked pattern to the bindings
+    // of the original.
+    std::map<const GenericBinding*, const GenericBinding*> param_map;
+    // TODO: Keep track of the instantiations we've already performed and don't
+    // do them again.
+  };
+
+  // Map from template declarations to extra information we use to type-check
+  // and instantiate the template.
+  std::map<const Declaration*, TemplateInfo> templates_;
 };
 
 }  // namespace Carbon

+ 2 - 0
explorer/syntax/lexer.lpp

@@ -121,6 +121,7 @@ SLASH                 "/"
 SLASH_EQUAL           "/="
 STAR_EQUAL            "*="
 STRING                "String"
+TEMPLATE              "template"
 THEN                  "then"
 TRUE                  "true"
 TYPE                  "type"
@@ -240,6 +241,7 @@ operand_start         [(A-Za-z0-9_\"]
 {SLASH}                 { return CARBON_SIMPLE_TOKEN(SLASH);                 }
 {STAR_EQUAL}            { return CARBON_SIMPLE_TOKEN(STAR_EQUAL);            }
 {STRING}                { return CARBON_SIMPLE_TOKEN(STRING);                }
+{TEMPLATE}              { return CARBON_SIMPLE_TOKEN(TEMPLATE);              }
 {THEN}                  { return CARBON_SIMPLE_TOKEN(THEN);                  }
 {TRUE}                  { return CARBON_SIMPLE_TOKEN(TRUE);                  }
 {TYPE}                  { return CARBON_SIMPLE_TOKEN(TYPE);                  }

+ 22 - 4
explorer/syntax/parser.ypp

@@ -298,6 +298,7 @@
   SLASH_EQUAL
   STAR_EQUAL
   STRING
+  TEMPLATE
   THEN
   TRUE
   TYPE
@@ -741,7 +742,8 @@ where_expression:
   type_expression WHERE where_clause_list
     {
       auto* self =
-          arena->New<GenericBinding>(context.source_loc(), ".Self", $1);
+          arena->New<GenericBinding>(context.source_loc(), ".Self", $1,
+                                     GenericBinding::BindingKind::Checked);
       $$ = arena->New<WhereExpression>(context.source_loc(), self, $3);
     }
 ;
@@ -850,7 +852,15 @@ non_expression_pattern:
                                       std::nullopt);
     }
 | binding_lhs COLON_BANG expression
-    { $$ = arena->New<GenericBinding>(context.source_loc(), $1, $3); }
+    {
+      $$ = arena->New<GenericBinding>(context.source_loc(), $1, $3,
+                                      GenericBinding::BindingKind::Checked);
+    }
+| TEMPLATE binding_lhs COLON_BANG expression
+    {
+      $$ = arena->New<GenericBinding>(context.source_loc(), $2, $4,
+                                      GenericBinding::BindingKind::Template);
+    }
 | paren_pattern
     { $$ = $1; }
 | postfix_expression tuple_pattern
@@ -1083,7 +1093,13 @@ return_term:
 generic_binding:
   identifier COLON_BANG expression
     {
-      $$ = arena->New<GenericBinding>(context.source_loc(), std::move($1), $3);
+      $$ = arena->New<GenericBinding>(context.source_loc(), std::move($1), $3,
+                                      GenericBinding::BindingKind::Checked);
+    }
+| TEMPLATE identifier COLON_BANG expression
+    {
+      $$ = arena->New<GenericBinding>(context.source_loc(), std::move($2), $4,
+                                      GenericBinding::BindingKind::Template);
     }
 ;
 deduced_param:
@@ -1253,7 +1269,9 @@ declaration:
 | MIXIN declared_name type_params mixin_import LEFT_CURLY_BRACE mixin_body RIGHT_CURLY_BRACE
     {
       // EXPERIMENTAL MIXN FEATURE
-      auto self = arena->New<GenericBinding>(context.source_loc(), "Self", $4);
+      auto self =
+          arena->New<GenericBinding>(context.source_loc(), "Self", $4,
+                                     GenericBinding::BindingKind::Checked);
       $$ = arena->New<MixinDeclaration>(context.source_loc(), std::move($2), $3,
                                         self, $6);
     }

+ 1 - 1
explorer/testdata/assoc_const/fail_anonymous.carbon

@@ -9,7 +9,7 @@
 package ExplorerTest api;
 
 interface Iface {
-  // CHECK:STDERR: SYNTAX ERROR: {{.*}}/explorer/testdata/assoc_const/fail_anonymous.carbon:[[@LINE+1]]: syntax error, unexpected UNDERSCORE, expecting identifier
+  // CHECK:STDERR: SYNTAX ERROR: {{.*}}/explorer/testdata/assoc_const/fail_anonymous.carbon:[[@LINE+1]]: syntax error, unexpected UNDERSCORE, expecting identifier or TEMPLATE
   let _:! type;
 }
 

+ 46 - 0
explorer/testdata/template/fail_name_lookup.carbon

@@ -0,0 +1,46 @@
+// 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
+// RUN: %{not} %{explorer-run}
+// RUN: %{not} %{explorer-run-trace}
+
+package ExplorerTest api;
+
+interface CallF {
+  fn DoIt[self: Self]();
+}
+
+interface HasF {
+  fn F[self: Self]();
+}
+
+impl forall [template T:! HasF] T as CallF {
+  // TODO: This case should be accepted, using `ClassWithExternalF.(HasF.F)`.
+  // TODO: The other case should be rejected due to ambiguity.
+  // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/template/fail_name_lookup.carbon:[[@LINE+1]]: class ClassWithExternalF does not have a field named F
+  fn DoIt[self: Self]() { self.F(); }
+}
+
+class ClassWithInternalF {
+  fn F[self: Self]() { Print("ClassWithInternalF.F"); }
+}
+
+external impl ClassWithInternalF as HasF {
+  fn F[self: Self]() { Print("ClassWithInternalF.(HasF.F)"); }
+}
+
+class ClassWithExternalF {}
+
+external impl ClassWithExternalF as HasF {
+  fn F[self: Self]() { Print("ClassWithExternalF.(HasF.F)"); }
+}
+
+fn Main() -> i32 {
+  var a: ClassWithInternalF = {};
+  var b: ClassWithExternalF = {};
+  a.(CallF.DoIt)();
+  b.(CallF.DoIt)();
+  return 0;
+}

+ 22 - 0
explorer/testdata/template/fail_no_member.carbon

@@ -0,0 +1,22 @@
+// 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
+// RUN: %{not} %{explorer-run}
+// RUN: %{not} %{explorer-run-trace}
+
+package ExplorerTest api;
+
+interface GetX {
+  fn DoIt[self: Self]() -> i32;
+}
+
+impl forall [template T:! type] T as GetX {
+  // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/template/fail_no_member.carbon:[[@LINE+1]]: member access, unexpected i32 in self.x
+  fn DoIt[self: Self]() -> i32 { return self.x; }
+}
+
+fn Main() -> i32 {
+  return 0.(GetX.DoIt)();
+}

+ 32 - 0
explorer/testdata/template/member_access.carbon

@@ -0,0 +1,32 @@
+// 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
+// RUN: %{explorer-run}
+// RUN: %{explorer-run-trace}
+// CHECK:STDOUT: 1
+// CHECK:STDOUT: 3
+// CHECK:STDOUT: result: 0
+
+package ExplorerTest api;
+
+interface GetX {
+  fn DoIt[self: Self]() -> i32;
+}
+
+impl forall [template T:! type] T as GetX {
+  fn DoIt[self: Self]() -> i32 { return self.x; }
+}
+
+class C {
+  var x: i32;
+}
+
+fn Main() -> i32 {
+  var a: auto = {.x = 1, .y = 2};
+  var b: C = {.x = 3};
+  Print("{0}", a.(GetX.DoIt)());
+  Print("{0}", b.(GetX.DoIt)());
+  return 0;
+}

+ 77 - 0
explorer/testdata/template/name_lookup.carbon

@@ -0,0 +1,77 @@
+// 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
+// RUN: %{explorer-run}
+// RUN: %{explorer-run-trace}
+// CHECK:STDOUT: MemberF.F
+// CHECK:STDOUT: ImplF.(HasF.F)
+// CHECK:STDOUT: BothFs.(HasF.F)
+// CHECK:STDOUT: BothFs.F
+// CHECK:STDOUT: BothFs.F
+// CHECK:STDOUT: result: 0
+
+package ExplorerTest api;
+
+choice ImplKind {
+  Checked,
+  ConstrainedTemplate,
+  UnconstrainedTemplate
+}
+
+interface CallF(K:! ImplKind) {
+  fn DoIt[self: Self]();
+}
+
+interface HasF {
+  fn F[self: Self]();
+}
+
+impl forall [T:! HasF] T as CallF(ImplKind.Checked) {
+  fn DoIt[self: Self]() { self.F(); }
+}
+
+impl forall [template T:! HasF] T as CallF(ImplKind.ConstrainedTemplate) {
+  fn DoIt[self: Self]() { self.F(); }
+}
+
+impl forall [template T:! type] T as CallF(ImplKind.UnconstrainedTemplate) {
+  fn DoIt[self: Self]() { self.F(); }
+}
+
+class MemberF {
+  fn F[self: Self]() { Print("MemberF.F"); }
+}
+
+class ImplF {}
+external impl ImplF as HasF {
+  fn F[self: Self]() { Print("ImplF.(HasF.F)"); }
+}
+
+class BothFs {
+  fn F[self: Self]() { Print("BothFs.F"); }
+}
+external impl BothFs as HasF {
+  fn F[self: Self]() { Print("BothFs.(HasF.F)"); }
+}
+
+fn Main() -> i32 {
+  var mem: MemberF = {};
+  var imp: ImplF = {};
+  var both: BothFs = {};
+
+  mem.(CallF(ImplKind.UnconstrainedTemplate).DoIt)();
+
+  imp.(CallF(ImplKind.Checked).DoIt)();
+  // TODO: Should be valid, but currently fails during instantiation.
+  //imp.(CallF(ImplKind.ConstrainedTemplate).DoIt)();
+
+  both.(CallF(ImplKind.Checked).DoIt)();
+  // TODO: Should be rejected, but currently incorrectly accepted.
+  // This line can be deleted once it starts failing; we test that this is
+  // rejected in fail_name_lookup.carbon.
+  both.(CallF(ImplKind.ConstrainedTemplate).DoIt)();
+  both.(CallF(ImplKind.UnconstrainedTemplate).DoIt)();
+  return 0;
+}