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

Basic support for parameterized interfaces. (#1220)

To make this work, introduce impl bindings into scope immediately when type-checking. We may rely on their existence before we finish type-checking the enclosing pattern.
Richard Smith 4 лет назад
Родитель
Сommit
3b58a1bf48

+ 1 - 1
executable_semantics/ast/declaration.cpp

@@ -80,7 +80,7 @@ void Declaration::PrintID(llvm::raw_ostream& out) const {
   switch (kind()) {
     case DeclarationKind::InterfaceDeclaration: {
       const auto& iface_decl = cast<InterfaceDeclaration>(*this);
-      out << "interface" << iface_decl.name();
+      out << "interface " << iface_decl.name();
       break;
     }
     case DeclarationKind::ImplDeclaration: {

+ 12 - 5
executable_semantics/ast/declaration.h

@@ -289,30 +289,37 @@ class InterfaceDeclaration : public Declaration {
   using ImplementsCarbonValueNode = void;
 
   InterfaceDeclaration(SourceLocation source_loc, std::string name,
+                       std::optional<Nonnull<TuplePattern*>> params,
                        Nonnull<GenericBinding*> self,
                        std::vector<Nonnull<Declaration*>> members)
       : Declaration(AstNodeKind::InterfaceDeclaration, source_loc),
         name_(std::move(name)),
-        members_(std::move(members)),
-        self_(self) {}
+        params_(std::move(params)),
+        self_(self),
+        members_(std::move(members)) {}
 
   static auto classof(const AstNode* node) -> bool {
     return InheritsFromInterfaceDeclaration(node->kind());
   }
 
   auto name() const -> const std::string& { return name_; }
-  auto members() const -> llvm::ArrayRef<Nonnull<Declaration*>> {
-    return members_;
+  auto params() const -> std::optional<Nonnull<const TuplePattern*>> {
+    return params_;
   }
+  auto params() -> std::optional<Nonnull<TuplePattern*>> { return params_; }
   auto self() const -> Nonnull<const GenericBinding*> { return self_; }
   auto self() -> Nonnull<GenericBinding*> { return self_; }
+  auto members() const -> llvm::ArrayRef<Nonnull<Declaration*>> {
+    return members_;
+  }
 
   auto value_category() const -> ValueCategory { return ValueCategory::Let; }
 
  private:
   std::string name_;
-  std::vector<Nonnull<Declaration*>> members_;
+  std::optional<Nonnull<TuplePattern*>> params_;
   Nonnull<GenericBinding*> self_;
+  std::vector<Nonnull<Declaration*>> members_;
 };
 
 enum class ImplKind { InternalImpl, ExternalImpl };

+ 31 - 14
executable_semantics/interpreter/interpreter.cpp

@@ -633,20 +633,37 @@ auto Interpreter::CallFunction(const CallExpression& call,
       const ClassDeclaration& class_decl = class_type.declaration();
       RuntimeScope type_params_scope(&heap_);
       BindingMap generic_args;
-      if (class_decl.type_params().has_value()) {
-        CHECK(PatternMatch(&(*class_decl.type_params())->value(), arg,
-                           call.source_loc(), &type_params_scope, generic_args,
-                           trace_stream_));
-        switch (phase()) {
-          case Phase::RunTime:
-            return todo_.FinishAction(arena_->New<NominalClassType>(
-                &class_type.declaration(), generic_args, witnesses));
-          case Phase::CompileTime:
-            return todo_.FinishAction(arena_->New<NominalClassType>(
-                &class_type.declaration(), generic_args, call.impls()));
-        }
-      } else {
-        FATAL() << "instantiation of non-generic class " << class_type;
+      CHECK(class_decl.type_params().has_value())
+          << "instantiation of non-generic class " << class_type;
+      CHECK(PatternMatch(&(*class_decl.type_params())->value(), arg,
+                         call.source_loc(), &type_params_scope, generic_args,
+                         trace_stream_));
+      switch (phase()) {
+        case Phase::RunTime:
+          return todo_.FinishAction(arena_->New<NominalClassType>(
+              &class_decl, generic_args, witnesses));
+        case Phase::CompileTime:
+          return todo_.FinishAction(arena_->New<NominalClassType>(
+              &class_decl, generic_args, call.impls()));
+      }
+    }
+    case Value::Kind::InterfaceType: {
+      const InterfaceType& iface_type = cast<InterfaceType>(*fun);
+      const InterfaceDeclaration& iface_decl = iface_type.declaration();
+      RuntimeScope params_scope(&heap_);
+      BindingMap generic_args;
+      CHECK(iface_decl.params().has_value())
+          << "call of unparameterized interface " << iface_type;
+      CHECK(PatternMatch(&(*iface_decl.params())->value(), arg,
+                         call.source_loc(), &params_scope, generic_args,
+                         trace_stream_));
+      switch (phase()) {
+        case Phase::RunTime:
+          return todo_.FinishAction(
+              arena_->New<InterfaceType>(&iface_decl, generic_args, witnesses));
+        case Phase::CompileTime:
+          return todo_.FinishAction(arena_->New<InterfaceType>(
+              &iface_decl, generic_args, call.impls()));
       }
     }
     default:

+ 3 - 0
executable_semantics/interpreter/resolve_names.cpp

@@ -303,6 +303,9 @@ static auto ResolveNames(Declaration& declaration, StaticScope& enclosing_scope)
       auto& iface = cast<InterfaceDeclaration>(declaration);
       StaticScope iface_scope;
       iface_scope.AddParent(&enclosing_scope);
+      if (iface.params().has_value()) {
+        RETURN_IF_ERROR(ResolveNames(**iface.params(), iface_scope));
+      }
       RETURN_IF_ERROR(iface_scope.Add("Self", iface.self()));
       for (Nonnull<Declaration*> member : iface.members()) {
         RETURN_IF_ERROR(AddExposedNames(*member, iface_scope));

+ 224 - 152
executable_semantics/interpreter/type_checker.cpp

@@ -86,7 +86,6 @@ static auto IsType(Nonnull<const Value*> value) -> bool {
     case Value::Kind::FunctionType:
     case Value::Kind::PointerType:
     case Value::Kind::StructType:
-    case Value::Kind::InterfaceType:
     case Value::Kind::ChoiceType:
     case Value::Kind::ContinuationType:
     case Value::Kind::VariableType:
@@ -105,14 +104,10 @@ static auto IsType(Nonnull<const Value*> value) -> bool {
       }
       return true;
     }
-    case Value::Kind::NominalClassType: {
-      const NominalClassType& class_type = cast<NominalClassType>(*value);
-      // A NominalClassType is concrete if
-      // 1) it is not a generic class (has no type parameters), or
-      // 2) it is a generic class applied to some type arguments.
-      return !class_type.declaration().type_params().has_value() ||
-             !class_type.type_args().empty();
-    }
+    case Value::Kind::NominalClassType:
+      return !cast<NominalClassType>(*value).IsParameterized();
+    case Value::Kind::InterfaceType:
+      return !cast<InterfaceType>(*value).IsParameterized();
   }
 }
 
@@ -152,7 +147,6 @@ static auto IsConcreteType(Nonnull<const Value*> value) -> bool {
     case Value::Kind::FunctionType:
     case Value::Kind::PointerType:
     case Value::Kind::StructType:
-    case Value::Kind::InterfaceType:
     case Value::Kind::ChoiceType:
     case Value::Kind::ContinuationType:
     case Value::Kind::VariableType:
@@ -173,14 +167,10 @@ static auto IsConcreteType(Nonnull<const Value*> value) -> bool {
       }
       return true;
     }
-    case Value::Kind::NominalClassType: {
-      const NominalClassType& class_type = cast<NominalClassType>(*value);
-      // A NominalClassType is concrete if
-      // 1) it is not a generic class (has no type parameters), or
-      // 2) it is a generic class applied to some type arguments.
-      return !class_type.declaration().type_params().has_value() ||
-             !class_type.type_args().empty();
-    }
+    case Value::Kind::NominalClassType:
+      return !cast<NominalClassType>(*value).IsParameterized();
+    case Value::Kind::InterfaceType:
+      return !cast<InterfaceType>(*value).IsParameterized();
   }
 }
 
@@ -303,9 +293,7 @@ auto TypeChecker::IsImplicitlyConvertible(
     case Value::Kind::InterfaceType:
       return destination->kind() == Value::Kind::TypeType;
     case Value::Kind::TypeOfClassType: {
-      const auto& class_type = cast<TypeOfClassType>(*source).class_type();
-      return ((!class_type.declaration().type_params().has_value()) ||
-              (!class_type.type_args().empty())) &&
+      return !cast<TypeOfClassType>(*source).class_type().IsParameterized() &&
              destination->kind() == Value::Kind::TypeType;
     }
     default:
@@ -538,12 +526,25 @@ auto TypeChecker::Substitute(
       }
       return new_class_type;
     }
+    case Value::Kind::InterfaceType: {
+      const auto& iface_type = cast<InterfaceType>(*type);
+      BindingMap args;
+      for (const auto& [name, value] : iface_type.args()) {
+        args[name] = Substitute(dict, value);
+      }
+      Nonnull<const InterfaceType*> new_iface_type =
+          arena_->New<InterfaceType>(&iface_type.declaration(), args);
+      if (trace_stream_) {
+        **trace_stream_ << "substitution: " << iface_type << " => "
+                        << *new_iface_type << "\n";
+      }
+      return new_iface_type;
+    }
     case Value::Kind::StaticArrayType:
     case Value::Kind::AutoType:
     case Value::Kind::IntType:
     case Value::Kind::BoolType:
     case Value::Kind::TypeType:
-    case Value::Kind::InterfaceType:
     case Value::Kind::ChoiceType:
     case Value::Kind::ContinuationType:
     case Value::Kind::StringType:
@@ -634,11 +635,12 @@ auto TypeChecker::SatisfyImpls(
     BindingMap& deduced_type_args, ImplExpMap& impls) const
     -> ErrorOr<Success> {
   for (Nonnull<const ImplBinding*> impl_binding : impl_bindings) {
-    ASSIGN_OR_RETURN(
-        Nonnull<Expression*> impl,
-        impl_scope.Resolve(impl_binding->interface(),
-                           deduced_type_args[impl_binding->type_var()],
-                           source_loc, *this));
+    Nonnull<const Value*> interface =
+        Substitute(deduced_type_args, impl_binding->interface());
+    ASSIGN_OR_RETURN(Nonnull<Expression*> impl,
+                     impl_scope.Resolve(
+                         interface, deduced_type_args[impl_binding->type_var()],
+                         source_loc, *this));
     impls.emplace(impl_binding, impl);
   }
   return Success();
@@ -839,11 +841,10 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
                       FindMember(access.field(), iface_decl.members());
                   member.has_value()) {
                 const Value& member_type = (*member)->static_type();
-                std::map<Nonnull<const GenericBinding*>, Nonnull<const Value*>>
-                    self_map;
-                self_map[iface_decl.self()] = &var_type;
+                BindingMap binding_map = iface_type.args();
+                binding_map[iface_decl.self()] = &var_type;
                 Nonnull<const Value*> inst_member_type =
-                    Substitute(self_map, &member_type);
+                    Substitute(binding_map, &member_type);
                 access.set_static_type(inst_member_type);
                 CHECK(var_type.binding().impl_binding().has_value());
                 access.set_impl(*var_type.binding().impl_binding());
@@ -877,8 +878,10 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
                   FindMember(access.field(), iface_decl.members());
               member.has_value()) {
             const Value& member_type = (*member)->static_type();
+            BindingMap binding_map = iface_type.args();
+            binding_map[iface_decl.self()] = &var_type;
             Nonnull<const Value*> inst_member_type =
-                Substitute({{iface_decl.self(), &var_type}}, &member_type);
+                Substitute(binding_map, &member_type);
             access.set_static_type(inst_member_type);
             CHECK(var_type.binding().impl_binding().has_value());
             access.set_impl(*var_type.binding().impl_binding());
@@ -1060,8 +1063,7 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
               cast<TypeOfClassType>(call.function().static_type()).class_type();
           const ClassDeclaration& class_decl = class_type.declaration();
           BindingMap generic_args;
-          if (class_decl.type_params().has_value() &&
-              class_type.type_args().empty()) {
+          if (class_type.IsParameterized()) {
             if (trace_stream_) {
               **trace_stream_ << "pattern matching type params and args\n";
             }
@@ -1089,23 +1091,11 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
             if (binding->impl_binding().has_value()) {
               Nonnull<const ImplBinding*> impl_binding =
                   *binding->impl_binding();
-              switch (impl_binding->interface()->kind()) {
-                case Value::Kind::InterfaceType: {
-                  ASSIGN_OR_RETURN(
-                      Nonnull<Expression*> impl,
-                      impl_scope.Resolve(impl_binding->interface(),
-                                         generic_args[binding],
-                                         call.source_loc(), *this));
-                  impls.emplace(impl_binding, impl);
-                  break;
-                }
-                case Value::Kind::TypeType:
-                  break;
-                default:
-                  return CompilationError(e->source_loc())
-                         << "unexpected type of deduced parameter "
-                         << *impl_binding->interface();
-              }
+              ASSIGN_OR_RETURN(Nonnull<Expression*> impl,
+                               impl_scope.Resolve(impl_binding->interface(),
+                                                  generic_args[binding],
+                                                  call.source_loc(), *this));
+              impls.emplace(impl_binding, impl);
             }
           }
           Nonnull<NominalClassType*> inst_class_type =
@@ -1115,6 +1105,59 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
           call.set_value_category(ValueCategory::Let);
           return Success();
         }
+        case Value::Kind::TypeOfInterfaceType: {
+          // This case handles the application of a parameterized class to
+          // its arguments, such as OrderedWith(i32).
+          // FIXME: This is very repetitive with the corresponding case for
+          // parameterized classes.
+          const InterfaceType& iface_type =
+              cast<TypeOfInterfaceType>(call.function().static_type())
+                  .interface_type();
+          const InterfaceDeclaration& iface_decl = iface_type.declaration();
+          BindingMap generic_args;
+          if (iface_type.IsParameterized()) {
+            if (trace_stream_) {
+              **trace_stream_ << "pattern matching interface params and args\n";
+            }
+            RETURN_IF_ERROR(ExpectType(call.source_loc(), "call",
+                                       &(*iface_decl.params())->static_type(),
+                                       &call.argument().static_type()));
+            ASSIGN_OR_RETURN(
+                Nonnull<const Value*> arg,
+                InterpExp(&call.argument(), arena_, trace_stream_));
+            CHECK(PatternMatch(&(*iface_decl.params())->value(), arg,
+                               call.source_loc(), std::nullopt, generic_args,
+                               trace_stream_));
+          } else if (!iface_type.args().empty()) {
+            return CompilationError(call.source_loc())
+                   << "attempt to provide arguments to parameterized interface "
+                   << "twice: " << *e;
+          } else {
+            return CompilationError(call.source_loc())
+                   << "attempt to provide arguments to non-parameterized "
+                   << "interface: " << *e;
+          }
+          // Find impls for all the impl bindings of the interface.
+          ImplExpMap impls;
+          for (const auto& [binding, val] : generic_args) {
+            if (binding->impl_binding().has_value()) {
+              Nonnull<const ImplBinding*> impl_binding =
+                  *binding->impl_binding();
+              ASSIGN_OR_RETURN(Nonnull<Expression*> impl,
+                               impl_scope.Resolve(impl_binding->interface(),
+                                                  generic_args[binding],
+                                                  call.source_loc(), *this));
+              impls.emplace(impl_binding, impl);
+            }
+          }
+          Nonnull<InterfaceType*> inst_iface_type =
+              arena_->New<InterfaceType>(&iface_decl, generic_args, impls);
+          call.set_impls(impls);
+          call.set_static_type(
+              arena_->New<TypeOfInterfaceType>(inst_iface_type));
+          call.set_value_category(ValueCategory::Let);
+          return Success();
+        }
         default: {
           return CompilationError(e->source_loc())
                  << "in call, expected a function\n"
@@ -1218,33 +1261,32 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
   }
 }
 
-void TypeChecker::AddPatternImpls(Nonnull<Pattern*> p, ImplScope& impl_scope) {
+void TypeChecker::CollectImplBindingsInPattern(
+    Nonnull<const Pattern*> p,
+    std::vector<Nonnull<const ImplBinding*>>& impl_bindings) {
   switch (p->kind()) {
     case PatternKind::GenericBinding: {
       auto& binding = cast<GenericBinding>(*p);
-      CHECK(binding.impl_binding().has_value());
-      Nonnull<const ImplBinding*> impl_binding = *binding.impl_binding();
-      auto impl_id = arena_->New<IdentifierExpression>(p->source_loc(), "impl");
-      impl_id->set_value_node(impl_binding);
-      impl_scope.Add(impl_binding->interface(),
-                     *impl_binding->type_var()->symbolic_identity(), impl_id);
+      if (binding.impl_binding().has_value()) {
+        impl_bindings.push_back(*binding.impl_binding());
+      }
       return;
     }
     case PatternKind::TuplePattern: {
       auto& tuple = cast<TuplePattern>(*p);
-      for (Nonnull<Pattern*> field : tuple.fields()) {
-        AddPatternImpls(field, impl_scope);
+      for (Nonnull<const Pattern*> field : tuple.fields()) {
+        CollectImplBindingsInPattern(field, impl_bindings);
       }
       return;
     }
     case PatternKind::AlternativePattern: {
       auto& alternative = cast<AlternativePattern>(*p);
-      AddPatternImpls(&alternative.arguments(), impl_scope);
+      CollectImplBindingsInPattern(&alternative.arguments(), impl_bindings);
       return;
     }
     case PatternKind::VarPattern: {
       auto& var_pattern = cast<VarPattern>(*p);
-      AddPatternImpls(&var_pattern.pattern(), impl_scope);
+      CollectImplBindingsInPattern(&var_pattern.pattern(), impl_bindings);
       return;
     }
     case PatternKind::ExpressionPattern:
@@ -1254,9 +1296,34 @@ void TypeChecker::AddPatternImpls(Nonnull<Pattern*> p, ImplScope& impl_scope) {
   }
 }
 
+void TypeChecker::BringPatternImplsIntoScope(Nonnull<const Pattern*> p,
+                                             ImplScope& impl_scope) {
+  std::vector<Nonnull<const ImplBinding*>> impl_bindings;
+  CollectImplBindingsInPattern(p, impl_bindings);
+  BringImplsIntoScope(impl_bindings, impl_scope);
+}
+
+void TypeChecker::BringImplsIntoScope(
+    llvm::ArrayRef<Nonnull<const ImplBinding*>> impl_bindings,
+    ImplScope& impl_scope) {
+  for (Nonnull<const ImplBinding*> impl_binding : impl_bindings) {
+    BringImplIntoScope(impl_binding, impl_scope);
+  }
+}
+
+void TypeChecker::BringImplIntoScope(Nonnull<const ImplBinding*> impl_binding,
+                                     ImplScope& impl_scope) {
+  CHECK(impl_binding->type_var()->symbolic_identity().has_value());
+  auto impl_id =
+      arena_->New<IdentifierExpression>(impl_binding->source_loc(), "impl");
+  impl_id->set_value_node(impl_binding);
+  impl_scope.Add(impl_binding->interface(),
+                 *impl_binding->type_var()->symbolic_identity(), impl_id);
+}
+
 auto TypeChecker::TypeCheckPattern(
     Nonnull<Pattern*> p, std::optional<Nonnull<const Value*>> expected,
-    const ImplScope& impl_scope, ValueCategory enclosing_value_category)
+    ImplScope& impl_scope, ValueCategory enclosing_value_category)
     -> ErrorOr<Success> {
   if (trace_stream_) {
     **trace_stream_ << "checking pattern " << *p;
@@ -1324,10 +1391,14 @@ auto TypeChecker::TypeCheckPattern(
       ASSIGN_OR_RETURN(Nonnull<const Value*> val,
                        InterpPattern(&binding, arena_, trace_stream_));
       binding.set_symbolic_identity(val);
-      Nonnull<ImplBinding*> impl_binding = arena_->New<ImplBinding>(
-          binding.source_loc(), &binding, &binding.static_type());
-      binding.set_impl_binding(impl_binding);
       SetValue(&binding, val);
+
+      if (isa<InterfaceType>(type)) {
+        Nonnull<ImplBinding*> impl_binding =
+            arena_->New<ImplBinding>(binding.source_loc(), &binding, type);
+        binding.set_impl_binding(impl_binding);
+        BringImplIntoScope(impl_binding, impl_scope);
+      }
       return Success();
     }
     case PatternKind::TuplePattern: {
@@ -1428,10 +1499,12 @@ auto TypeChecker::TypeCheckStmt(Nonnull<Statement*> s,
       RETURN_IF_ERROR(TypeCheckExp(&match.expression(), impl_scope));
       std::vector<Match::Clause> new_clauses;
       for (auto& clause : match.clauses()) {
+        ImplScope clause_scope;
+        clause_scope.AddParent(&impl_scope);
         RETURN_IF_ERROR(TypeCheckPattern(&clause.pattern(),
                                          &match.expression().static_type(),
-                                         impl_scope, ValueCategory::Let));
-        RETURN_IF_ERROR(TypeCheckStmt(&clause.statement(), impl_scope));
+                                         clause_scope, ValueCategory::Let));
+        RETURN_IF_ERROR(TypeCheckStmt(&clause.statement(), clause_scope));
       }
       return Success();
     }
@@ -1458,7 +1531,16 @@ auto TypeChecker::TypeCheckStmt(Nonnull<Statement*> s,
       auto& var = cast<VariableDefinition>(*s);
       RETURN_IF_ERROR(TypeCheckExp(&var.init(), impl_scope));
       const Value& rhs_ty = var.init().static_type();
-      RETURN_IF_ERROR(TypeCheckPattern(&var.pattern(), &rhs_ty, impl_scope,
+      // FIXME: If the pattern contains a binding that implies a new impl is
+      // available, should that remain in scope for as long as its binding?
+      // ```
+      // var a: (T:! Widget) = ...;
+      // // Is the `impl T as Widget` in scope here?
+      // a.(Widget.F)();
+      // ```
+      ImplScope var_scope;
+      var_scope.AddParent(&impl_scope);
+      RETURN_IF_ERROR(TypeCheckPattern(&var.pattern(), &rhs_ty, var_scope,
                                        var.value_category()));
       return Success();
     }
@@ -1604,45 +1686,6 @@ auto TypeChecker::ExpectReturnOnAllPaths(
   }
 }
 
-auto TypeChecker::CreateImplBindings(
-    llvm::ArrayRef<Nonnull<GenericBinding*>> deduced_parameters,
-    SourceLocation source_loc,
-    std::vector<Nonnull<const ImplBinding*>>& impl_bindings)
-    -> ErrorOr<Success> {
-  for (Nonnull<GenericBinding*> deduced : deduced_parameters) {
-    switch (deduced->static_type().kind()) {
-      case Value::Kind::InterfaceType: {
-        Nonnull<ImplBinding*> impl_binding = arena_->New<ImplBinding>(
-            deduced->source_loc(), deduced, &deduced->static_type());
-        deduced->set_impl_binding(impl_binding);
-        impl_binding->set_static_type(&deduced->static_type());
-        impl_bindings.push_back(impl_binding);
-        break;
-      }
-      case Value::Kind::TypeType:
-        // No `impl` binding needed for type parameter with bound `Type`.
-        break;
-      default:
-        return CompilationError(source_loc)
-               << "unexpected type of deduced parameter "
-               << deduced->static_type();
-    }
-  }
-  return Success();
-}
-
-void TypeChecker::BringImplsIntoScope(
-    llvm::ArrayRef<Nonnull<const ImplBinding*>> impl_bindings, ImplScope& scope,
-    SourceLocation source_loc) {
-  for (Nonnull<const ImplBinding*> impl_binding : impl_bindings) {
-    CHECK(impl_binding->type_var()->symbolic_identity().has_value());
-    auto impl_id = arena_->New<IdentifierExpression>(source_loc, "impl");
-    impl_id->set_value_node(impl_binding);
-    scope.Add(impl_binding->interface(),
-              *impl_binding->type_var()->symbolic_identity(), impl_id);
-  }
-}
-
 // TODO: Add checking to function definitions to ensure that
 //   all deduced type parameters will be deduced.
 auto TypeChecker::DeclareFunctionDeclaration(Nonnull<FunctionDeclaration*> f,
@@ -1651,30 +1694,25 @@ auto TypeChecker::DeclareFunctionDeclaration(Nonnull<FunctionDeclaration*> f,
   if (trace_stream_) {
     **trace_stream_ << "** declaring function " << f->name() << "\n";
   }
+  ImplScope function_scope;
+  function_scope.AddParent(&enclosing_scope);
+  std::vector<Nonnull<const ImplBinding*>> impl_bindings;
   // Bring the deduced parameters into scope.
   for (Nonnull<GenericBinding*> deduced : f->deduced_parameters()) {
-    RETURN_IF_ERROR(TypeCheckExp(&deduced->type(), enclosing_scope));
-    deduced->set_symbolic_identity(arena_->New<VariableType>(deduced));
-    ASSIGN_OR_RETURN(Nonnull<const Value*> type_of_type,
-                     InterpExp(&deduced->type(), arena_, trace_stream_));
-    deduced->set_static_type(type_of_type);
+    RETURN_IF_ERROR(TypeCheckPattern(deduced, std::nullopt, function_scope,
+                                     ValueCategory::Let));
+    CollectImplBindingsInPattern(deduced, impl_bindings);
   }
-  // Create the impl_bindings.
-  std::vector<Nonnull<const ImplBinding*>> impl_bindings;
-  RETURN_IF_ERROR(CreateImplBindings(f->deduced_parameters(), f->source_loc(),
-                                     impl_bindings));
-  // Bring the impl bindings into scope.
-  ImplScope function_scope;
-  function_scope.AddParent(&enclosing_scope);
-  BringImplsIntoScope(impl_bindings, function_scope, f->source_loc());
   // Type check the receiver pattern.
   if (f->is_method()) {
     RETURN_IF_ERROR(TypeCheckPattern(&f->me_pattern(), std::nullopt,
                                      function_scope, ValueCategory::Let));
+    CollectImplBindingsInPattern(&f->me_pattern(), impl_bindings);
   }
   // Type check the parameter pattern.
   RETURN_IF_ERROR(TypeCheckPattern(&f->param_pattern(), std::nullopt,
                                    function_scope, ValueCategory::Let));
+  CollectImplBindingsInPattern(&f->param_pattern(), impl_bindings);
 
   // Evaluate the return type, if we can do so without examining the body.
   if (std::optional<Nonnull<Expression*>> return_expression =
@@ -1737,11 +1775,11 @@ auto TypeChecker::TypeCheckFunctionDeclaration(Nonnull<FunctionDeclaration*> f,
   // if f->return_term().is_auto(), the function body was already
   // type checked in DeclareFunctionDeclaration.
   if (f->body().has_value() && !f->return_term().is_auto()) {
-    // Bring the impl's into scope.
+    // Bring the impls into scope.
     ImplScope function_scope;
     function_scope.AddParent(&impl_scope);
     BringImplsIntoScope(cast<FunctionType>(f->static_type()).impl_bindings(),
-                        function_scope, f->source_loc());
+                        function_scope);
     if (trace_stream_)
       **trace_stream_ << function_scope;
     RETURN_IF_ERROR(TypeCheckStmt(*f->body(), function_scope));
@@ -1766,7 +1804,6 @@ auto TypeChecker::DeclareClassDeclaration(Nonnull<ClassDeclaration*> class_decl,
     class_scope.AddParent(&enclosing_scope);
     RETURN_IF_ERROR(TypeCheckPattern(*class_decl->type_params(), std::nullopt,
                                      class_scope, ValueCategory::Let));
-    AddPatternImpls(*class_decl->type_params(), class_scope);
     if (trace_stream_) {
       **trace_stream_ << class_scope;
     }
@@ -1779,8 +1816,6 @@ auto TypeChecker::DeclareClassDeclaration(Nonnull<ClassDeclaration*> class_decl,
     for (Nonnull<Declaration*> m : class_decl->members()) {
       RETURN_IF_ERROR(DeclareDeclaration(m, class_scope));
     }
-
-    // TODO: when/how to bring impls in generic class into scope?
   } else {
     // The declarations of the members may refer to the class, so we
     // must set the constant value of the class and its static type
@@ -1810,7 +1845,7 @@ auto TypeChecker::TypeCheckClassDeclaration(
   ImplScope class_scope;
   class_scope.AddParent(&impl_scope);
   if (class_decl->type_params().has_value()) {
-    AddPatternImpls(*class_decl->type_params(), class_scope);
+    BringPatternImplsIntoScope(*class_decl->type_params(), class_scope);
   }
   if (trace_stream_) {
     **trace_stream_ << class_scope;
@@ -1828,15 +1863,33 @@ auto TypeChecker::TypeCheckClassDeclaration(
 auto TypeChecker::DeclareInterfaceDeclaration(
     Nonnull<InterfaceDeclaration*> iface_decl, ImplScope& enclosing_scope)
     -> ErrorOr<Success> {
+  if (trace_stream_) {
+    **trace_stream_ << "** declaring interface " << iface_decl->name() << "\n";
+  }
+  ImplScope iface_scope;
+  iface_scope.AddParent(&enclosing_scope);
+
+  if (iface_decl->params().has_value()) {
+    RETURN_IF_ERROR(TypeCheckPattern(*iface_decl->params(), std::nullopt,
+                                     iface_scope, ValueCategory::Let));
+    if (trace_stream_) {
+      **trace_stream_ << iface_scope;
+    }
+  }
+
   Nonnull<InterfaceType*> iface_type = arena_->New<InterfaceType>(iface_decl);
   SetConstantValue(iface_decl, iface_type);
   iface_decl->set_static_type(arena_->New<TypeOfInterfaceType>(iface_type));
 
   // Process the Self parameter.
   RETURN_IF_ERROR(TypeCheckPattern(iface_decl->self(), std::nullopt,
-                                   enclosing_scope, ValueCategory::Let));
+                                   iface_scope, ValueCategory::Let));
   for (Nonnull<Declaration*> m : iface_decl->members()) {
-    RETURN_IF_ERROR(DeclareDeclaration(m, enclosing_scope));
+    RETURN_IF_ERROR(DeclareDeclaration(m, iface_scope));
+  }
+  if (trace_stream_) {
+    **trace_stream_ << "** finished declaring interface " << iface_decl->name()
+                    << "\n";
   }
   return Success();
 }
@@ -1844,8 +1897,23 @@ auto TypeChecker::DeclareInterfaceDeclaration(
 auto TypeChecker::TypeCheckInterfaceDeclaration(
     Nonnull<InterfaceDeclaration*> iface_decl, const ImplScope& impl_scope)
     -> ErrorOr<Success> {
+  if (trace_stream_) {
+    **trace_stream_ << "** checking interface " << iface_decl->name() << "\n";
+  }
+  ImplScope iface_scope;
+  iface_scope.AddParent(&impl_scope);
+  if (iface_decl->params().has_value()) {
+    BringPatternImplsIntoScope(*iface_decl->params(), iface_scope);
+  }
+  if (trace_stream_) {
+    **trace_stream_ << iface_scope;
+  }
   for (Nonnull<Declaration*> m : iface_decl->members()) {
-    RETURN_IF_ERROR(TypeCheckDeclaration(m, impl_scope));
+    RETURN_IF_ERROR(TypeCheckDeclaration(m, iface_scope));
+  }
+  if (trace_stream_) {
+    **trace_stream_ << "** finished checking interface " << iface_decl->name()
+                    << "\n";
   }
   return Success();
 }
@@ -1857,29 +1925,35 @@ auto TypeChecker::DeclareImplDeclaration(Nonnull<ImplDeclaration*> impl_decl,
     **trace_stream_ << "declaring " << *impl_decl << "\n";
   }
   RETURN_IF_ERROR(TypeCheckExp(&impl_decl->interface(), enclosing_scope));
-  ASSIGN_OR_RETURN(Nonnull<const Value*> iface_type,
+  ASSIGN_OR_RETURN(Nonnull<const Value*> written_iface_type,
                    InterpExp(&impl_decl->interface(), arena_, trace_stream_));
-  const auto& iface_decl = cast<InterfaceType>(*iface_type).declaration();
+
+  const auto* iface_type = dyn_cast<InterfaceType>(written_iface_type);
+  if (!iface_type) {
+    return CompilationError(impl_decl->interface().source_loc())
+           << "expected constraint after `as`, found value of type "
+           << *written_iface_type;
+  }
+  if (iface_type->IsParameterized()) {
+    return CompilationError(impl_decl->interface().source_loc())
+           << "missing arguments for parameterized interface";
+  }
+
+  const auto& iface_decl = iface_type->declaration();
   impl_decl->set_interface_type(iface_type);
 
+  ImplScope impl_scope;
+  impl_scope.AddParent(&enclosing_scope);
+  std::vector<Nonnull<const ImplBinding*>> impl_bindings;
+
   // Bring the deduced parameters into scope.
   for (Nonnull<GenericBinding*> deduced : impl_decl->deduced_parameters()) {
-    RETURN_IF_ERROR(TypeCheckExp(&deduced->type(), enclosing_scope));
-    deduced->set_symbolic_identity(arena_->New<VariableType>(deduced));
-    ASSIGN_OR_RETURN(Nonnull<const Value*> type_of_type,
-                     InterpExp(&deduced->type(), arena_, trace_stream_));
-    deduced->set_static_type(type_of_type);
+    RETURN_IF_ERROR(TypeCheckPattern(deduced, std::nullopt, impl_scope,
+                                     ValueCategory::Let));
+    CollectImplBindingsInPattern(deduced, impl_bindings);
   }
-  // Create the impl_bindings.
-  std::vector<Nonnull<const ImplBinding*>> impl_bindings;
-  RETURN_IF_ERROR(CreateImplBindings(impl_decl->deduced_parameters(),
-                                     impl_decl->source_loc(), impl_bindings));
   impl_decl->set_impl_bindings(impl_bindings);
 
-  // Bring the impl bindings into scope for the impl body.
-  ImplScope impl_scope;
-  impl_scope.AddParent(&enclosing_scope);
-  BringImplsIntoScope(impl_bindings, impl_scope, impl_decl->source_loc());
   // Check and interpret the impl_type
   RETURN_IF_ERROR(TypeCheckExp(impl_decl->impl_type(), impl_scope));
   ASSIGN_OR_RETURN(Nonnull<const Value*> impl_type_value,
@@ -1902,11 +1976,10 @@ auto TypeChecker::DeclareImplDeclaration(Nonnull<ImplDeclaration*> impl_decl,
       if (std::optional<Nonnull<const Declaration*>> mem =
               FindMember(*mem_name, impl_decl->members());
           mem.has_value()) {
-        std::map<Nonnull<const GenericBinding*>, Nonnull<const Value*>>
-            self_map;
-        self_map[iface_decl.self()] = impl_type_value;
+        BindingMap binding_map = iface_type->args();
+        binding_map[iface_decl.self()] = impl_type_value;
         Nonnull<const Value*> iface_mem_type =
-            Substitute(self_map, &m->static_type());
+            Substitute(binding_map, &m->static_type());
         RETURN_IF_ERROR(ExpectType((*mem)->source_loc(),
                                    "member of implementation", iface_mem_type,
                                    &(*mem)->static_type()));
@@ -1930,11 +2003,10 @@ auto TypeChecker::TypeCheckImplDeclaration(Nonnull<ImplDeclaration*> impl_decl,
   if (trace_stream_) {
     **trace_stream_ << "checking " << *impl_decl << "\n";
   }
-  // Bring the impl's from the parameters into scope.
+  // Bring the impls from the parameters into scope.
   ImplScope impl_scope;
   impl_scope.AddParent(&enclosing_scope);
-  BringImplsIntoScope(impl_decl->impl_bindings(), impl_scope,
-                      impl_decl->source_loc());
+  BringImplsIntoScope(impl_decl->impl_bindings(), impl_scope);
   for (Nonnull<Declaration*> m : impl_decl->members()) {
     RETURN_IF_ERROR(TypeCheckDeclaration(m, impl_scope));
   }

+ 20 - 17
executable_semantics/interpreter/type_checker.h

@@ -64,9 +64,11 @@ class TypeChecker {
   // `expected` is the type that this pattern is expected to have, if the
   // surrounding context gives us that information. Otherwise, it is
   // nullopt.
+  //
+  // `impl_scope` is extended with all impls implied by the pattern.
   auto TypeCheckPattern(Nonnull<Pattern*> p,
                         std::optional<Nonnull<const Value*>> expected,
-                        const ImplScope& impl_scope,
+                        ImplScope& impl_scope,
                         ValueCategory enclosing_value_category)
       -> ErrorOr<Success>;
 
@@ -104,8 +106,24 @@ class TypeChecker {
                                 const ImplScope& enclosing_scope)
       -> ErrorOr<Success>;
 
+  // Find all of the ImplBindings in the given pattern. The pattern is required
+  // to have already been type-checked.
+  void CollectImplBindingsInPattern(
+      Nonnull<const Pattern*> p,
+      std::vector<Nonnull<const ImplBinding*>>& impl_bindings);
+
   // Add the impls from the pattern into the given `impl_scope`.
-  void AddPatternImpls(Nonnull<Pattern*> p, ImplScope& impl_scope);
+  void BringPatternImplsIntoScope(Nonnull<const Pattern*> p,
+                                  ImplScope& impl_scope);
+
+  // Add the given ImplBinding to the given `impl_scope`.
+  void BringImplIntoScope(Nonnull<const ImplBinding*> impl_binding,
+                          ImplScope& impl_scope);
+
+  // Add all of the `impl_bindings` into the `scope`.
+  void BringImplsIntoScope(
+      llvm::ArrayRef<Nonnull<const ImplBinding*>> impl_bindings,
+      ImplScope& scope);
 
   // Checks the statements and (runtime) expressions within the
   // declaration, such as the body of a function.
@@ -184,21 +202,6 @@ class TypeChecker {
                                  Nonnull<const Value*>>& dict,
                   Nonnull<const Value*> type) const -> Nonnull<const Value*>;
 
-  // For each deduced type parameter of a generic that has a
-  // non-trivial type (such as an interface), create an impl binding
-  // to serve as the parameter for passing a witness at runtime for
-  // the required impl.
-  auto CreateImplBindings(
-      llvm::ArrayRef<Nonnull<GenericBinding*>> deduced_parameters,
-      SourceLocation source_loc,
-      std::vector<Nonnull<const ImplBinding*>>& impl_bindings)
-      -> ErrorOr<Success>;
-
-  // Add all of the `impl_bindings` into the `scope`.
-  void BringImplsIntoScope(
-      llvm::ArrayRef<Nonnull<const ImplBinding*>> impl_bindings,
-      ImplScope& scope, SourceLocation source_loc);
-
   // Find impls that satisfy all of the `impl_bindings`, but with the
   // type variables in the `impl_bindings` replaced by the argument
   // type in `deduced_type_args`.  The results are placed in the

+ 20 - 4
executable_semantics/interpreter/value.cpp

@@ -334,6 +334,14 @@ void Value::Print(llvm::raw_ostream& out) const {
     case Value::Kind::InterfaceType: {
       const auto& iface_type = cast<InterfaceType>(*this);
       out << "interface " << iface_type.declaration().name();
+      if (!iface_type.args().empty()) {
+        out << "(";
+        llvm::ListSeparator sep;
+        for (const auto& [bind, val] : iface_type.args()) {
+          out << sep << bind->name() << " = " << *val;
+        }
+        out << ")";
+      }
       break;
     }
     case Value::Kind::Witness: {
@@ -456,15 +464,23 @@ auto TypeEqual(Nonnull<const Value*> t1, Nonnull<const Value*> t2) -> bool {
       }
       for (const auto& [ty_var1, ty1] :
            cast<NominalClassType>(*t1).type_args()) {
-        if (!TypeEqual(ty1,
-                       cast<NominalClassType>(*t2).type_args().at(ty_var1))) {
+        if (!ValueEqual(ty1,
+                        cast<NominalClassType>(*t2).type_args().at(ty_var1))) {
           return false;
         }
       }
       return true;
     case Value::Kind::InterfaceType:
-      return cast<InterfaceType>(*t1).declaration().name() ==
-             cast<InterfaceType>(*t2).declaration().name();
+      if (cast<InterfaceType>(*t1).declaration().name() !=
+          cast<InterfaceType>(*t2).declaration().name()) {
+        return false;
+      }
+      for (const auto& [ty_var1, ty1] : cast<InterfaceType>(*t1).args()) {
+        if (!ValueEqual(ty1, cast<InterfaceType>(*t2).args().at(ty_var1))) {
+          return false;
+        }
+      }
+      return true;
     case Value::Kind::ChoiceType:
       return cast<ChoiceType>(*t1).name() == cast<ChoiceType>(*t2).name();
     case Value::Kind::TupleValue: {

+ 33 - 0
executable_semantics/interpreter/value.h

@@ -560,6 +560,12 @@ class NominalClassType : public Value {
   // instantiated runtime type of a generic class.
   auto witnesses() const -> const ImplWitnessMap& { return witnesses_; }
 
+  // Returns whether this a parameterized class. That is, a class with
+  // parameters and no corresponding arguments.
+  auto IsParameterized() const -> bool {
+    return declaration_->type_params().has_value() && type_args_.empty();
+  }
+
   // Returns the value of the function named `name` in this class, or
   // nullopt if there is no such function.
   auto FindFunction(const std::string& name) const
@@ -582,6 +588,21 @@ class InterfaceType : public Value {
  public:
   explicit InterfaceType(Nonnull<const InterfaceDeclaration*> declaration)
       : Value(Kind::InterfaceType), declaration_(declaration) {}
+  explicit InterfaceType(Nonnull<const InterfaceDeclaration*> declaration,
+                         const BindingMap& args)
+      : Value(Kind::InterfaceType), declaration_(declaration), args_(args) {}
+  explicit InterfaceType(Nonnull<const InterfaceDeclaration*> declaration,
+                         const BindingMap& args, const ImplExpMap& impls)
+      : Value(Kind::InterfaceType),
+        declaration_(declaration),
+        args_(args),
+        impls_(impls) {}
+  explicit InterfaceType(Nonnull<const InterfaceDeclaration*> declaration,
+                         const BindingMap& args, const ImplWitnessMap& wits)
+      : Value(Kind::InterfaceType),
+        declaration_(declaration),
+        args_(args),
+        witnesses_(wits) {}
 
   static auto classof(const Value* value) -> bool {
     return value->kind() == Kind::InterfaceType;
@@ -590,9 +611,21 @@ class InterfaceType : public Value {
   auto declaration() const -> const InterfaceDeclaration& {
     return *declaration_;
   }
+  auto args() const -> const BindingMap& { return args_; }
+
+  // FIXME: These aren't used for anything yet.
+  auto impls() const -> const ImplExpMap& { return impls_; }
+  auto witnesses() const -> const ImplWitnessMap& { return witnesses_; }
+
+  auto IsParameterized() const -> bool {
+    return declaration_->params().has_value() && args_.empty();
+  }
 
  private:
   Nonnull<const InterfaceDeclaration*> declaration_;
+  BindingMap args_;
+  ImplExpMap impls_;
+  ImplWitnessMap witnesses_;
 };
 
 // The witness table for an impl.

+ 3 - 2
executable_semantics/syntax/parser.ypp

@@ -909,12 +909,13 @@ declaration:
       $$ = arena->New<VariableDeclaration>(context.source_loc(), $2, $4,
                                            ValueCategory::Let);
     }
-| INTERFACE identifier LEFT_CURLY_BRACE declaration_list RIGHT_CURLY_BRACE
+| INTERFACE identifier type_params LEFT_CURLY_BRACE declaration_list RIGHT_CURLY_BRACE
     {
       auto ty_ty = arena -> New<TypeTypeLiteral>(context.source_loc());
       auto self =
           arena -> New<GenericBinding>(context.source_loc(), "Self", ty_ty);
-      $$ = arena->New<InterfaceDeclaration>(context.source_loc(), $2, self, $4);
+      $$ = arena->New<InterfaceDeclaration>(context.source_loc(), $2, $3, self,
+                                            $5);
     }
 | impl_kind IMPL impl_deduced_params expression AS expression LEFT_CURLY_BRACE declaration_list RIGHT_CURLY_BRACE
     {

+ 1 - 1
executable_semantics/testdata/generic_class/fail_bad_parameter_type.carbon

@@ -7,7 +7,7 @@
 // RUN: %{not} %{executable_semantics} --parser_debug --trace_file=- %s 2>&1 | \
 // RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
 // AUTOUPDATE: %{executable_semantics} %s
-// CHECK: COMPILATION ERROR: {{.*}}/executable_semantics/testdata/generic_class/fail_bad_parameter_type.carbon:15: unexpected type of deduced parameter i32
+// CHECK: COMPILATION ERROR: {{.*}}/executable_semantics/testdata/generic_class/fail_bad_parameter_type.carbon:28: type error in call: '(Type)' is not implicitly convertible to '(i32)'
 
 package ExecutableSemanticsTest api;
 

+ 17 - 0
executable_semantics/testdata/impl/fail_impl_as_not_constraint.carbon

@@ -0,0 +1,17 @@
+// 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
+//
+// RUN: %{not} %{executable_semantics} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{not} %{executable_semantics} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{executable_semantics} %s
+// CHECK: COMPILATION ERROR: {{.*}}/executable_semantics/testdata/impl/fail_impl_as_not_constraint.carbon:14: expected constraint after `as`, found value of type i32
+
+package ExecutableSemanticsTest api;
+
+external impl i32 as i32 {}
+
+fn Main() -> i32 {
+}

+ 20 - 0
executable_semantics/testdata/impl/fail_impl_as_parameterized.carbon

@@ -0,0 +1,20 @@
+// 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
+//
+// RUN: %{not} %{executable_semantics} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{not} %{executable_semantics} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{executable_semantics} %s
+// CHECK: COMPILATION ERROR: {{.*}}/executable_semantics/testdata/impl/fail_impl_as_parameterized.carbon:17: missing arguments for parameterized interface
+
+package ExecutableSemanticsTest api;
+
+interface Vector(Scalar:! Type) {
+}
+
+external impl i32 as Vector {}
+
+fn Main() -> i32 {
+}

+ 17 - 0
executable_semantics/testdata/impl/impl_as.carbon

@@ -0,0 +1,17 @@
+// 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
+//
+// RUN: %{not} %{executable_semantics} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{not} %{executable_semantics} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{executable_semantics} %s
+// CHECK: COMPILATION ERROR: {{.*}}/executable_semantics/testdata/impl/impl_as.carbon:14: expected constraint after `as`, found value of type i32
+
+package ExecutableSemanticsTest api;
+
+external impl i32 as i32 {}
+
+fn Main() -> i32 {
+}

+ 66 - 0
executable_semantics/testdata/interface/constrained_parameter.carbon

@@ -0,0 +1,66 @@
+// 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
+//
+// RUN: %{executable_semantics} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{executable_semantics} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{executable_semantics} %s
+// CHECK: result: 0
+
+package ExecutableSemanticsTest api;
+
+interface AddMul {
+  fn Add[me: Self](o: Self) -> Self;
+  fn Mul[me: Self](o: Self) -> Self;
+}
+
+external impl i32 as AddMul {
+  fn Add[me: i32](o: i32) -> i32 {
+    return me + o;
+  }
+  fn Mul[me: i32](o: i32) -> i32 {
+    return me * o;
+  }
+}
+
+class Holder(T:! AddMul) {
+  var v: T;
+}
+
+interface Vector(Scalar:! AddMul) {
+  fn Zero() -> Self;
+  fn Add[me: Self](b: Self) -> Self;
+  fn Scale[me: Self](v: Scalar) -> Self;
+  fn Hold[me: Self](v: Scalar) -> Holder(Scalar);
+}
+
+class Point {
+  var x: i32;
+  var y: i32;
+  impl Point as Vector(i32) {
+    fn Zero() -> Point {
+      return {.x = 0, .y = 0};
+    }
+    fn Add[me: Point](b: Point) -> Point {
+      return {.x = me.x + b.x, .y = me.y + b.y};
+    }
+    fn Scale[me: Point](v: i32) -> Point {
+      return {.x = me.x * v, .y = me.y * v};
+    }
+    fn Hold[me: Point](v: i32) -> Holder(i32) {
+      return {.v = v};
+    }
+  }
+}
+
+fn AddAndScaleGeneric[T:! AddMul, U:! Vector(T)](a: U, s: T) -> U {
+  return a.Add(U.Zero()).Scale(a.Hold(s).v);
+}
+
+fn Main() -> i32 {
+  var a: Point = {.x = 2, .y = 1};
+  var p: Point = AddAndScaleGeneric(a, 5);
+  return p.x - 10;
+}

+ 44 - 0
executable_semantics/testdata/interface/parameterized.carbon

@@ -0,0 +1,44 @@
+// 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
+//
+// RUN: %{executable_semantics} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{executable_semantics} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{executable_semantics} %s
+// CHECK: result: 0
+
+package ExecutableSemanticsTest api;
+
+interface Vector(Scalar:! Type) {
+  fn Zero() -> Self;
+  fn Add[me: Self](b: Self) -> Self;
+  fn Scale[me: Self](v: Scalar) -> Self;
+}
+
+class Point {
+  var x: i32;
+  var y: i32;
+  impl Point as Vector(i32) {
+    fn Zero() -> Point {
+      return {.x = 0, .y = 0};
+    }
+    fn Add[me: Point](b: Point) -> Point {
+      return {.x = me.x + b.x, .y = me.y + b.y};
+    }
+    fn Scale[me: Point](v: i32) -> Point {
+      return {.x = me.x * v, .y = me.y * v};
+    }
+  }
+}
+
+fn AddAndScaleGeneric[T:! Type, U:! Vector(T)](a: U, s: T) -> U {
+  return a.Add(U.Zero()).Scale(s);
+}
+
+fn Main() -> i32 {
+  var a: Point = {.x = 2, .y = 1};
+  var p: Point = AddAndScaleGeneric(a, 5);
+  return p.x - 10;
+}