Przeglądaj źródła

Initial support for constraints formed by combining interfaces (#1307)

Add scaffolding for constraints in general, and support specifically constraints formed by applying a `&` operator.

No support for constraints formed with `where` nor for named constraints at this point.
Richard Smith 3 lat temu
rodzic
commit
1bb370420b

+ 1 - 0
common/fuzzing/carbon.proto

@@ -52,6 +52,7 @@ message PrimitiveOperatorExpression {
     Or = 9;
     Sub = 10;
     Ptr = 11;
+    Combine = 12;
   }
   optional Operator op = 1;
   repeated Expression arguments = 2;

+ 4 - 0
common/fuzzing/proto_to_carbon.cpp

@@ -146,6 +146,10 @@ static auto PrimitiveOperatorToCarbon(
     case Fuzzing::PrimitiveOperatorExpression::Or:
       BinaryOperatorToCarbon(arg0, " or ", arg1, out);
       break;
+
+    case Fuzzing::PrimitiveOperatorExpression::Combine:
+      BinaryOperatorToCarbon(arg0, " & ", arg1, out);
+      break;
   }
   out << ")";
 }

+ 1 - 0
explorer/ast/expression.cpp

@@ -55,6 +55,7 @@ auto ToString(Operator op) -> std::string_view {
     case Operator::Add:
       return "+";
     case Operator::AddressOf:
+    case Operator::Combine:
       return "&";
     case Operator::Neg:
     case Operator::Sub:

+ 21 - 3
explorer/ast/expression.h

@@ -26,6 +26,7 @@ namespace Carbon {
 class Value;
 class MemberName;
 class VariableType;
+class InterfaceType;
 class ImplBinding;
 
 class Expression : public AstNode {
@@ -111,6 +112,7 @@ enum class Operator {
   Add,
   AddressOf,
   And,
+  Combine,
   Deref,
   Eq,
   Mul,
@@ -168,6 +170,7 @@ class SimpleMemberAccessExpression : public Expression {
   auto object() const -> const Expression& { return *object_; }
   auto object() -> Expression& { return *object_; }
   auto member() const -> const std::string& { return member_; }
+
   // Returns true if the field is a method that has a "me" declaration in an
   // AddrPattern.
   auto is_field_addr_me_method() const -> bool {
@@ -180,21 +183,36 @@ class SimpleMemberAccessExpression : public Expression {
   // If `object` has a generic type, returns the `ImplBinding` that
   // identifies its witness table. Otherwise, returns `std::nullopt`. Should not
   // be called before typechecking.
-  auto impl() const -> std::optional<Nonnull<const ImplBinding*>> {
+  auto impl() const -> std::optional<Nonnull<const Expression*>> {
     return impl_;
   }
 
   // Can only be called once, during typechecking.
-  void set_impl(Nonnull<const ImplBinding*> impl) {
+  void set_impl(Nonnull<const Expression*> impl) {
     CARBON_CHECK(!impl_.has_value());
     impl_ = impl;
   }
 
+  // If `object` is a constrained type parameter and `member` was found in an
+  // interface, returns that interface. Should not be called before
+  // typechecking.
+  auto found_in_interface() const
+      -> std::optional<Nonnull<const InterfaceType*>> {
+    return found_in_interface_;
+  }
+
+  // Can only be called once, during typechecking.
+  void set_found_in_interface(Nonnull<const InterfaceType*> interface) {
+    CARBON_CHECK(!found_in_interface_.has_value());
+    found_in_interface_ = interface;
+  }
+
  private:
   Nonnull<Expression*> object_;
   std::string member_;
-  std::optional<Nonnull<const ImplBinding*>> impl_;
   bool is_field_addr_me_method_ = false;
+  std::optional<Nonnull<const Expression*>> impl_;
+  std::optional<Nonnull<const InterfaceType*>> found_in_interface_;
 };
 
 // A compound member access expression of the form `object.(path)`.

+ 2 - 0
explorer/fuzzing/ast_to_proto.cpp

@@ -56,6 +56,8 @@ static auto OperatorToProtoEnum(const Operator op)
       return Fuzzing::PrimitiveOperatorExpression::Or;
     case Operator::Sub:
       return Fuzzing::PrimitiveOperatorExpression::Sub;
+    case Operator::Combine:
+      return Fuzzing::PrimitiveOperatorExpression::Combine;
   }
 }
 

+ 54 - 14
explorer/interpreter/impl_scope.cpp

@@ -10,34 +10,78 @@
 #include "llvm/Support/Casting.h"
 
 using llvm::cast;
+using llvm::dyn_cast;
 
 namespace Carbon {
 
 void ImplScope::Add(Nonnull<const Value*> iface, Nonnull<const Value*> type,
-                    Nonnull<Expression*> impl) {
-  Add(iface, {}, type, {}, impl);
+                    Nonnull<Expression*> impl,
+                    const TypeChecker& type_checker) {
+  Add(iface, {}, type, {}, impl, type_checker);
 }
 
 void ImplScope::Add(Nonnull<const Value*> iface,
                     llvm::ArrayRef<Nonnull<const GenericBinding*>> deduced,
                     Nonnull<const Value*> type,
                     llvm::ArrayRef<Nonnull<const ImplBinding*>> impl_bindings,
-                    Nonnull<Expression*> impl) {
-  impls_.push_back({.interface = iface,
+                    Nonnull<Expression*> impl_expr,
+                    const TypeChecker& type_checker) {
+  if (auto* constraint = dyn_cast<ConstraintType>(iface)) {
+    BindingMap map;
+    map[constraint->self_binding()] = type;
+    for (size_t i = 0; i != constraint->impl_constraints().size(); ++i) {
+      ConstraintType::ImplConstraint impl = constraint->impl_constraints()[i];
+      Add(cast<InterfaceType>(type_checker.Substitute(map, impl.interface)),
+          deduced, type_checker.Substitute(map, impl.type), impl_bindings,
+          type_checker.MakeConstraintWitnessAccess(impl_expr, i), type_checker);
+    }
+    return;
+  }
+
+  impls_.push_back({.interface = cast<InterfaceType>(iface),
                     .deduced = deduced,
                     .type = type,
                     .impl_bindings = impl_bindings,
-                    .impl = impl});
+                    .impl = impl_expr});
 }
 
 void ImplScope::AddParent(Nonnull<const ImplScope*> parent) {
   parent_scopes_.push_back(parent);
 }
 
-auto ImplScope::Resolve(Nonnull<const Value*> iface_type,
-                        Nonnull<const Value*> type, SourceLocation source_loc,
+auto ImplScope::Resolve(Nonnull<const Value*> constraint_type,
+                        Nonnull<const Value*> impl_type,
+                        SourceLocation source_loc,
                         const TypeChecker& type_checker) const
     -> ErrorOr<Nonnull<Expression*>> {
+  if (const auto* iface_type = dyn_cast<InterfaceType>(constraint_type)) {
+    return ResolveInterface(iface_type, impl_type, source_loc, type_checker);
+  }
+  if (const auto* constraint = dyn_cast<ConstraintType>(constraint_type)) {
+    std::vector<Nonnull<Expression*>> witnesses;
+    BindingMap map;
+    map[constraint->self_binding()] = impl_type;
+    for (auto impl : constraint->impl_constraints()) {
+      CARBON_ASSIGN_OR_RETURN(
+          Nonnull<Expression*> result,
+          ResolveInterface(
+              cast<InterfaceType>(type_checker.Substitute(map, impl.interface)),
+              type_checker.Substitute(map, impl.type), source_loc,
+              type_checker));
+      witnesses.push_back(result);
+    }
+    // TODO: Check satisfaction of same-type constraints.
+    return type_checker.MakeConstraintWitness(*constraint, std::move(witnesses),
+                                              source_loc);
+  }
+  CARBON_FATAL() << "expected a constraint, not " << *constraint_type;
+}
+
+auto ImplScope::ResolveInterface(Nonnull<const InterfaceType*> iface_type,
+                                 Nonnull<const Value*> type,
+                                 SourceLocation source_loc,
+                                 const TypeChecker& type_checker) const
+    -> ErrorOr<Nonnull<Expression*>> {
   CARBON_ASSIGN_OR_RETURN(
       std::optional<Nonnull<Expression*>> result,
       TryResolve(iface_type, type, source_loc, *this, type_checker));
@@ -48,7 +92,7 @@ auto ImplScope::Resolve(Nonnull<const Value*> iface_type,
   return *result;
 }
 
-auto ImplScope::TryResolve(Nonnull<const Value*> iface_type,
+auto ImplScope::TryResolve(Nonnull<const InterfaceType*> iface_type,
                            Nonnull<const Value*> type,
                            SourceLocation source_loc,
                            const ImplScope& original_scope,
@@ -73,20 +117,16 @@ auto ImplScope::TryResolve(Nonnull<const Value*> iface_type,
   return result;
 }
 
-auto ImplScope::ResolveHere(Nonnull<const Value*> iface_type,
+auto ImplScope::ResolveHere(Nonnull<const InterfaceType*> iface_type,
                             Nonnull<const Value*> impl_type,
                             SourceLocation source_loc,
                             const ImplScope& original_scope,
                             const TypeChecker& type_checker) const
     -> ErrorOr<std::optional<Nonnull<Expression*>>> {
-  if (iface_type->kind() != Value::Kind::InterfaceType) {
-    CARBON_FATAL() << "expected an interface, not " << *iface_type;
-  }
-  const auto& iface = cast<InterfaceType>(*iface_type);
   std::optional<Nonnull<Expression*>> result = std::nullopt;
   for (const Impl& impl : impls_) {
     std::optional<Nonnull<Expression*>> m = type_checker.MatchImpl(
-        iface, impl_type, impl, original_scope, source_loc);
+        *iface_type, impl_type, impl, original_scope, source_loc);
     if (m.has_value()) {
       if (result.has_value()) {
         return CompilationError(source_loc)

+ 17 - 8
explorer/interpreter/impl_scope.h

@@ -42,23 +42,23 @@ class ImplScope {
  public:
   // Associates `iface` and `type` with the `impl` in this scope.
   void Add(Nonnull<const Value*> iface, Nonnull<const Value*> type,
-           Nonnull<Expression*> impl);
+           Nonnull<Expression*> impl, const TypeChecker& type_checker);
   // For a parameterized impl, associates `iface` and `type`
   // with the `impl` in this scope.
   void Add(Nonnull<const Value*> iface,
            llvm::ArrayRef<Nonnull<const GenericBinding*>> deduced,
            Nonnull<const Value*> type,
            llvm::ArrayRef<Nonnull<const ImplBinding*>> impl_bindings,
-           Nonnull<Expression*> impl);
+           Nonnull<Expression*> impl, const TypeChecker& type_checker);
 
   // Make `parent` a parent of this scope.
   // REQUIRES: `parent` is not already a parent of this scope.
   void AddParent(Nonnull<const ImplScope*> parent);
 
-  // Returns the associated impl for the given `iface` and `type` in
+  // Returns the associated impl for the given `constraint` and `type` in
   // the ancestor graph of this scope, or reports a compilation error
   // at `source_loc` there isn't exactly one matching impl.
-  auto Resolve(Nonnull<const Value*> iface, Nonnull<const Value*> type,
+  auto Resolve(Nonnull<const Value*> constraint, Nonnull<const Value*> type,
                SourceLocation source_loc, const TypeChecker& type_checker) const
       -> ErrorOr<Nonnull<Expression*>>;
 
@@ -73,7 +73,7 @@ class ImplScope {
   // are non-empty. The former contains the type parameters and the
   // later are impl bindings, that is, parameters for witnesses.
   struct Impl {
-    Nonnull<const Value*> interface;
+    Nonnull<const InterfaceType*> interface;
     std::vector<Nonnull<const GenericBinding*>> deduced;
     Nonnull<const Value*> type;
     std::vector<Nonnull<const ImplBinding*>> impl_bindings;
@@ -81,14 +81,23 @@ class ImplScope {
   };
 
  private:
+  // Returns the associated impl for the given `iface` and `type` in
+  // the ancestor graph of this scope, or reports a compilation error
+  // at `source_loc` there isn't exactly one matching impl.
+  auto ResolveInterface(Nonnull<const InterfaceType*> iface,
+                        Nonnull<const Value*> type, SourceLocation source_loc,
+                        const TypeChecker& type_checker) const
+      -> ErrorOr<Nonnull<Expression*>>;
+
   // Returns the associated impl for the given `iface` and `type` in
   // the ancestor graph of this scope, returns std::nullopt if there
   // is none, or reports a compilation error is there is not a most
   // specific impl for the given `iface` and `type`.
   // Use `original_scope` to satisfy requirements of any generic impl
   // that matches `iface` and `type`.
-  auto TryResolve(Nonnull<const Value*> iface, Nonnull<const Value*> type,
-                  SourceLocation source_loc, const ImplScope& original_scope,
+  auto TryResolve(Nonnull<const InterfaceType*> iface_type,
+                  Nonnull<const Value*> type, SourceLocation source_loc,
+                  const ImplScope& original_scope,
                   const TypeChecker& type_checker) const
       -> ErrorOr<std::optional<Nonnull<Expression*>>>;
 
@@ -98,7 +107,7 @@ class ImplScope {
   // given `iface` and `type`.
   // Use `original_scope` to satisfy requirements of any generic impl
   // that matches `iface` and `type`.
-  auto ResolveHere(Nonnull<const Value*> iface_type,
+  auto ResolveHere(Nonnull<const InterfaceType*> iface_type,
                    Nonnull<const Value*> impl_type, SourceLocation source_loc,
                    const ImplScope& original_scope,
                    const TypeChecker& type_checker) const

+ 26 - 29
explorer/interpreter/interpreter.cpp

@@ -84,7 +84,8 @@ class Interpreter {
                     const std::vector<Nonnull<const Value*>>& values)
       -> Nonnull<const Value*>;
 
-  auto EvalPrim(Operator op, const std::vector<Nonnull<const Value*>>& args,
+  auto EvalPrim(Operator op, Nonnull<const Value*> static_type,
+                const std::vector<Nonnull<const Value*>>& args,
                 SourceLocation source_loc) -> ErrorOr<Nonnull<const Value*>>;
 
   // Returns the result of converting `value` to type `destination_type`.
@@ -158,7 +159,7 @@ void Interpreter::PrintState(llvm::raw_ostream& out) {
   out << "\n}\n";
 }
 
-auto Interpreter::EvalPrim(Operator op,
+auto Interpreter::EvalPrim(Operator op, Nonnull<const Value*> static_type,
                            const std::vector<Nonnull<const Value*>>& args,
                            SourceLocation source_loc)
     -> ErrorOr<Nonnull<const Value*>> {
@@ -190,6 +191,8 @@ auto Interpreter::EvalPrim(Operator op,
       return heap_.Read(cast<PointerValue>(*args[0]).address(), source_loc);
     case Operator::AddressOf:
       return arena_->New<PointerValue>(cast<LValue>(*args[0]).address());
+    case Operator::Combine:
+      return &cast<TypeOfConstraintType>(static_type)->constraint_type();
   }
 }
 
@@ -473,7 +476,7 @@ auto Interpreter::InstantiateType(Nonnull<const Value*> type,
         CARBON_ASSIGN_OR_RETURN(inst_type_args[ty_var],
                                 InstantiateType(ty_arg, source_loc));
       }
-      std::map<Nonnull<const ImplBinding*>, Nonnull<const Witness*>> witnesses;
+      ImplWitnessMap witnesses;
       for (const auto& [bind, impl_exp] : class_type.impls()) {
         CARBON_ASSIGN_OR_RETURN(witnesses[bind], EvalImplExp(impl_exp));
       }
@@ -506,6 +509,7 @@ auto Interpreter::Convert(Nonnull<const Value*> value,
     case Value::Kind::AutoType:
     case Value::Kind::NominalClassType:
     case Value::Kind::InterfaceType:
+    case Value::Kind::ConstraintType:
     case Value::Kind::Witness:
     case Value::Kind::ParameterizedEntityName:
     case Value::Kind::ChoiceType:
@@ -519,6 +523,7 @@ auto Interpreter::Convert(Nonnull<const Value*> value,
     case Value::Kind::StringValue:
     case Value::Kind::TypeOfClassType:
     case Value::Kind::TypeOfInterfaceType:
+    case Value::Kind::TypeOfConstraintType:
     case Value::Kind::TypeOfChoiceType:
     case Value::Kind::TypeOfParameterizedEntityName:
     case Value::Kind::TypeOfMemberName:
@@ -814,51 +819,43 @@ auto Interpreter::StepExp() -> ErrorOr<Success> {
     }
     case ExpressionKind::SimpleMemberAccessExpression: {
       const auto& access = cast<SimpleMemberAccessExpression>(exp);
+      bool forming_member_name = isa<TypeOfMemberName>(&access.static_type());
       if (act.pos() == 0) {
-        //    { { e.f :: C, E, F} :: S, H}
-        // -> { { e :: [].f :: C, E, F} :: S, H}
+        // First, evaluate the first operand.
         if (access.is_field_addr_me_method()) {
           return todo_.Spawn(std::make_unique<LValAction>(&access.object()));
         } else {
           return todo_.Spawn(
               std::make_unique<ExpressionAction>(&access.object()));
         }
+      } else if (act.pos() == 1 && access.impl().has_value() &&
+                 !forming_member_name) {
+        // Next, if we're accessing an interface member, evaluate the `impl`
+        // expression to find the corresponding witness.
+        return todo_.Spawn(
+            std::make_unique<ExpressionAction>(access.impl().value()));
       } else {
-        //    { { v :: [].f :: C, E, F} :: S, H}
-        // -> { { v_f :: C, E, F} : S, H}
+        // Finally, produce the result.
         if (const auto* member_name_type =
                 dyn_cast<TypeOfMemberName>(&access.static_type())) {
           // The result is a member name, such as in `Type.field_name`. Form a
           // suitable member name value.
           CARBON_CHECK(phase() == Phase::CompileTime)
               << "should not form MemberNames at runtime";
-          std::optional<const InterfaceType*> iface_result;
           std::optional<const Value*> type_result;
-          if (auto* iface_type = dyn_cast<InterfaceType>(act.results()[0])) {
-            iface_result = iface_type;
-          } else {
+          if (!isa<InterfaceType, ConstraintType>(act.results()[0])) {
             type_result = act.results()[0];
-            if (access.impl().has_value()) {
-              iface_result =
-                  cast<InterfaceType>(access.impl().value()->interface());
-            }
           }
-          MemberName* member_name = arena_->New<MemberName>(
-              type_result, iface_result, member_name_type->member());
+          MemberName* member_name =
+              arena_->New<MemberName>(type_result, access.found_in_interface(),
+                                      member_name_type->member());
           return todo_.FinishAction(member_name);
         } else {
           // The result is the value of the named field, such as in
           // `value.field_name`. Extract the value within the given object.
           std::optional<Nonnull<const Witness*>> witness;
           if (access.impl().has_value()) {
-            CARBON_ASSIGN_OR_RETURN(
-                auto witness_addr,
-                todo_.ValueOfNode(*access.impl(), access.source_loc()));
-            CARBON_ASSIGN_OR_RETURN(
-                Nonnull<const Value*> witness_value,
-                heap_.Read(llvm::cast<LValue>(witness_addr)->address(),
-                           access.source_loc()));
-            witness = cast<Witness>(witness_value);
+            witness = cast<Witness>(act.results()[1]);
           }
           FieldPath::Component member(access.member(), witness);
           const Value* aggregate;
@@ -963,9 +960,9 @@ auto Interpreter::StepExp() -> ErrorOr<Success> {
       } else {
         //    { {v :: op(vs,[]) :: C, E, F} :: S, H}
         // -> { {eval_prim(op, (vs,v)) :: C, E, F} :: S, H}
-        CARBON_ASSIGN_OR_RETURN(
-            Nonnull<const Value*> value,
-            EvalPrim(op.op(), act.results(), exp.source_loc()));
+        CARBON_ASSIGN_OR_RETURN(Nonnull<const Value*> value,
+                                EvalPrim(op.op(), &op.static_type(),
+                                         act.results(), exp.source_loc()));
         return todo_.FinishAction(value);
       }
     }
@@ -995,7 +992,7 @@ auto Interpreter::StepExp() -> ErrorOr<Success> {
         if (num_impls > 0) {
           int i = 2;
           for (const auto& [impl_bind, impl_exp] : call.impls()) {
-            witnesses[impl_bind] = cast<Witness>(act.results()[i]);
+            witnesses[impl_bind] = act.results()[i];
             ++i;
           }
         }

+ 322 - 100
explorer/interpreter/type_checker.cpp

@@ -107,8 +107,10 @@ static auto IsTypeOfType(Nonnull<const Value*> value) -> bool {
       return false;
     case Value::Kind::TypeType:
     case Value::Kind::InterfaceType:
+    case Value::Kind::ConstraintType:
     case Value::Kind::TypeOfClassType:
     case Value::Kind::TypeOfInterfaceType:
+    case Value::Kind::TypeOfConstraintType:
     case Value::Kind::TypeOfChoiceType:
       // A value of one of these types is itself always a type.
       return true;
@@ -150,12 +152,14 @@ static auto IsType(Nonnull<const Value*> value, bool concrete = false) -> bool {
     case Value::Kind::StructType:
     case Value::Kind::NominalClassType:
     case Value::Kind::InterfaceType:
+    case Value::Kind::ConstraintType:
     case Value::Kind::ChoiceType:
     case Value::Kind::ContinuationType:
     case Value::Kind::VariableType:
     case Value::Kind::StringType:
     case Value::Kind::TypeOfClassType:
     case Value::Kind::TypeOfInterfaceType:
+    case Value::Kind::TypeOfConstraintType:
     case Value::Kind::TypeOfChoiceType:
     case Value::Kind::StaticArrayType:
       return true;
@@ -356,15 +360,18 @@ auto TypeChecker::IsImplicitlyConvertible(
     case Value::Kind::TypeType:
       // TODO: This seems suspicious. Shouldn't this require that the type
       // implements the interface?
-      if (destination->kind() == Value::Kind::InterfaceType) {
+      if (isa<InterfaceType, ConstraintType>(destination)) {
         return true;
       }
       break;
     case Value::Kind::InterfaceType:
+    case Value::Kind::ConstraintType:
     case Value::Kind::TypeOfClassType:
     case Value::Kind::TypeOfChoiceType:
-      // TODO: These types should presumably also convert to interface types.
-      if (destination->kind() == Value::Kind::TypeType) {
+    case Value::Kind::TypeOfInterfaceType:
+    case Value::Kind::TypeOfConstraintType:
+      // TODO: These types should presumably also convert to constraint types.
+      if (isa<TypeType>(destination)) {
         return true;
       }
       break;
@@ -684,12 +691,14 @@ auto TypeChecker::ArgumentDeduction(
       // TODO: We could deduce the array type from an array or tuple argument.
     case Value::Kind::ContinuationType:
     case Value::Kind::ChoiceType:
+    case Value::Kind::ConstraintType:
     case Value::Kind::IntType:
     case Value::Kind::BoolType:
     case Value::Kind::TypeType:
     case Value::Kind::StringType:
     case Value::Kind::TypeOfClassType:
     case Value::Kind::TypeOfInterfaceType:
+    case Value::Kind::TypeOfConstraintType:
     case Value::Kind::TypeOfChoiceType:
     case Value::Kind::TypeOfParameterizedEntityName:
     case Value::Kind::TypeOfMemberName:
@@ -727,6 +736,14 @@ auto TypeChecker::ArgumentDeduction(
 auto TypeChecker::Substitute(
     const std::map<Nonnull<const GenericBinding*>, Nonnull<const Value*>>& dict,
     Nonnull<const Value*> type) const -> Nonnull<const Value*> {
+  auto SubstituteIntoBindingMap = [&](const BindingMap& map) -> BindingMap {
+    BindingMap result;
+    for (const auto& [name, value] : map) {
+      result[name] = Substitute(dict, value);
+    }
+    return result;
+  };
+
   switch (type->kind()) {
     case Value::Kind::VariableType: {
       auto it = dict.find(&cast<VariableType>(*type).binding());
@@ -766,12 +783,10 @@ auto TypeChecker::Substitute(
     }
     case Value::Kind::NominalClassType: {
       const auto& class_type = cast<NominalClassType>(*type);
-      BindingMap type_args;
-      for (const auto& [name, value] : class_type.type_args()) {
-        type_args[name] = Substitute(dict, value);
-      }
       Nonnull<const NominalClassType*> new_class_type =
-          arena_->New<NominalClassType>(&class_type.declaration(), type_args);
+          arena_->New<NominalClassType>(
+              &class_type.declaration(),
+              SubstituteIntoBindingMap(class_type.type_args()));
       if (trace_stream_) {
         **trace_stream_ << "substitution: " << class_type << " => "
                         << *new_class_type << "\n";
@@ -780,18 +795,56 @@ auto TypeChecker::Substitute(
     }
     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);
+      Nonnull<const InterfaceType*> new_iface_type = arena_->New<InterfaceType>(
+          &iface_type.declaration(),
+          SubstituteIntoBindingMap(iface_type.args()));
       if (trace_stream_) {
         **trace_stream_ << "substitution: " << iface_type << " => "
                         << *new_iface_type << "\n";
       }
       return new_iface_type;
     }
+    case Value::Kind::ConstraintType: {
+      const auto& constraint = cast<ConstraintType>(*type);
+      std::vector<ConstraintType::ImplConstraint> impl_constraints;
+      impl_constraints.reserve(constraint.impl_constraints().size());
+      for (const auto& impl_constraint : constraint.impl_constraints()) {
+        impl_constraints.push_back(
+            {.type = Substitute(dict, impl_constraint.type),
+             .interface = cast<InterfaceType>(
+                 Substitute(dict, impl_constraint.interface))});
+      }
+
+      std::vector<ConstraintType::EqualityConstraint> equality_constraints;
+      equality_constraints.reserve(constraint.equality_constraints().size());
+      for (const auto& equality_constraint :
+           constraint.equality_constraints()) {
+        std::vector<Nonnull<const Value*>> values;
+        for (const Value* value : equality_constraint.values) {
+          values.push_back(Substitute(dict, value));
+        }
+        equality_constraints.push_back({.values = values});
+      }
+      // TODO: Coalesce same-type constraints that are now overlapping.
+
+      std::vector<ConstraintType::LookupContext> lookup_contexts;
+      lookup_contexts.reserve(constraint.lookup_contexts().size());
+      for (const auto& lookup_context : constraint.lookup_contexts()) {
+        lookup_contexts.push_back(
+            {.context = Substitute(dict, lookup_context.context)});
+      }
+      // TODO: If the self_binding is substituted, should we track that
+      // somehow?
+      Nonnull<const ConstraintType*> new_constraint =
+          arena_->New<ConstraintType>(
+              constraint.self_binding(), std::move(impl_constraints),
+              std::move(equality_constraints), std::move(lookup_contexts));
+      if (trace_stream_) {
+        **trace_stream_ << "substitution: " << constraint << " => "
+                        << *new_constraint << "\n";
+      }
+      return new_constraint;
+    }
     case Value::Kind::StaticArrayType:
     case Value::Kind::AutoType:
     case Value::Kind::IntType:
@@ -800,11 +853,15 @@ auto TypeChecker::Substitute(
     case Value::Kind::ChoiceType:
     case Value::Kind::ContinuationType:
     case Value::Kind::StringType:
+      return type;
     case Value::Kind::TypeOfClassType:
     case Value::Kind::TypeOfInterfaceType:
+    case Value::Kind::TypeOfConstraintType:
     case Value::Kind::TypeOfChoiceType:
     case Value::Kind::TypeOfParameterizedEntityName:
     case Value::Kind::TypeOfMemberName:
+      // TODO: We should substitute into the value and produce a new type of
+      // type for it.
       return type;
     case Value::Kind::Witness:
     case Value::Kind::ParameterizedEntityName:
@@ -855,6 +912,8 @@ auto TypeChecker::MatchImpl(const InterfaceType& iface,
     return std::nullopt;
   }
 
+  // TODO: If the `interface` is a ConstraintType, for every impl_constraint,
+  // try deduction against that.
   if (ErrorOr<Success> e = ArgumentDeduction(
           source_loc, "match", impl.deduced, deduced_args, impl.interface,
           &iface, /*allow_implicit_conversion=*/false, impl_scope);
@@ -895,6 +954,22 @@ auto TypeChecker::MatchImpl(const InterfaceType& iface,
                                     source_loc, impl.impl, deduced_args, impls);
 }
 
+auto TypeChecker::MakeConstraintWitness(
+    const ConstraintType& constraint,
+    std::vector<Nonnull<Expression*>> impl_constraint_witnesses,
+    SourceLocation source_loc) const -> Nonnull<Expression*> {
+  return arena_->New<TupleLiteral>(source_loc,
+                                   std::move(impl_constraint_witnesses));
+}
+
+auto TypeChecker::MakeConstraintWitnessAccess(Nonnull<Expression*> witness,
+                                              size_t impl_offset) const
+    -> Nonnull<Expression*> {
+  return arena_->New<IndexExpression>(
+      witness->source_loc(), witness,
+      arena_->New<IntLiteral>(witness->source_loc(), impl_offset));
+}
+
 auto TypeChecker::SatisfyImpls(
     llvm::ArrayRef<Nonnull<const ImplBinding*>> impl_bindings,
     const ImplScope& impl_scope, SourceLocation source_loc,
@@ -913,6 +988,80 @@ auto TypeChecker::SatisfyImpls(
   return Success();
 }
 
+auto TypeChecker::MakeConstraintForInterface(
+    SourceLocation source_loc, Nonnull<const InterfaceType*> iface_type)
+    -> Nonnull<const ConstraintType*> {
+  auto* self_binding = arena_->New<GenericBinding>(
+      source_loc, ".Self", arena_->New<TypeTypeLiteral>(source_loc));
+  auto* self = arena_->New<VariableType>(self_binding);
+  std::vector<ConstraintType::ImplConstraint> impl_constraints = {
+      ConstraintType::ImplConstraint{.type = self, .interface = iface_type}};
+  std::vector<ConstraintType::EqualityConstraint> equality_constraints = {};
+  std::vector<ConstraintType::LookupContext> lookup_contexts = {
+      {.context = iface_type}};
+  return arena_->New<ConstraintType>(self_binding, std::move(impl_constraints),
+                                     std::move(equality_constraints),
+                                     std::move(lookup_contexts));
+}
+
+auto TypeChecker::CombineConstraints(
+    SourceLocation source_loc,
+    llvm::ArrayRef<Nonnull<const ConstraintType*>> constraints)
+    -> Nonnull<const ConstraintType*> {
+  auto* self_binding = arena_->New<GenericBinding>(
+      source_loc, ".Self", arena_->New<TypeTypeLiteral>(source_loc));
+  auto* self = arena_->New<VariableType>(self_binding);
+  std::vector<ConstraintType::ImplConstraint> impl_constraints;
+  std::vector<ConstraintType::EqualityConstraint> equality_constraints;
+  std::vector<ConstraintType::LookupContext> lookup_contexts;
+  for (Nonnull<const ConstraintType*> constraint : constraints) {
+    BindingMap map;
+    map[constraint->self_binding()] = self;
+    // TODO: Remove duplicates
+    for (ConstraintType::ImplConstraint impl : constraint->impl_constraints()) {
+      impl_constraints.push_back(
+          {.type = Substitute(map, impl.type),
+           .interface = cast<InterfaceType>(Substitute(map, impl.interface))});
+    }
+    for (ConstraintType::EqualityConstraint same :
+         constraint->equality_constraints()) {
+      std::vector<Nonnull<const Value*>> values;
+      for (const Value* value : same.values) {
+        values.push_back(Substitute(map, value));
+      }
+      auto AddEqualityConstraint =
+          [&](std::vector<Nonnull<const Value*>> values) {
+            // TODO: This is really inefficient. Use value canonicalization or
+            // hashing or similar to avoid the quadratic scan here.
+            for (const Value* value : values) {
+              for (ConstraintType::EqualityConstraint& existing :
+                   equality_constraints) {
+                for (const Value* existing_value : existing.values) {
+                  if (ValueEqual(value, existing_value)) {
+                    // There is overlap between two equality constraints.
+                    // Combine them into a single constraint.
+                    // TODO: Remove duplicates
+                    existing.values.insert(existing.values.end(),
+                                           values.begin(), values.end());
+                    return;
+                  }
+                }
+              }
+            }
+            equality_constraints.push_back({.values = std::move(values)});
+          };
+      AddEqualityConstraint(std::move(values));
+    }
+    // TODO: Remove duplicates
+    for (ConstraintType::LookupContext lookup : constraint->lookup_contexts()) {
+      lookup_contexts.push_back({.context = Substitute(map, lookup.context)});
+    }
+  }
+  return arena_->New<ConstraintType>(self_binding, std::move(impl_constraints),
+                                     std::move(equality_constraints),
+                                     std::move(lookup_contexts));
+}
+
 auto TypeChecker::DeduceCallBindings(
     CallExpression& call, Nonnull<const Value*> params_type,
     llvm::ArrayRef<FunctionType::GenericParameter> generic_params,
@@ -990,6 +1139,69 @@ auto TypeChecker::DeduceCallBindings(
   return Success();
 }
 
+struct ConstraintLookupResult {
+  Nonnull<const InterfaceType*> interface;
+  Nonnull<const Declaration*> member;
+  Nonnull<const Expression*> impl;
+};
+
+/// Look up a member name in a constraint, which might be a single interface or
+/// a compound constraint.
+static auto LookupInConstraint(SourceLocation source_loc,
+                               Nonnull<const Value*> type,
+                               const std::string& member_name)
+    -> ErrorOr<ConstraintLookupResult> {
+  // Find the set of lookup contexts.
+  llvm::ArrayRef<ConstraintType::LookupContext> lookup_contexts;
+  ConstraintType::LookupContext interface_context[1];
+  if (const auto* iface_type = dyn_cast<InterfaceType>(type)) {
+    // For an interface, look into that interface alone.
+    // TODO: Also look into any interfaces extended by it.
+    interface_context[0].context = iface_type;
+    lookup_contexts = interface_context;
+  } else if (const auto* constraint_type = dyn_cast<ConstraintType>(type)) {
+    // For a constraint, look in all of its lookup contexts.
+    lookup_contexts = constraint_type->lookup_contexts();
+  } else {
+    // Other kinds of constraint, such as TypeType, have no lookup contexts.
+  }
+
+  std::optional<ConstraintLookupResult> found;
+  for (ConstraintType::LookupContext lookup : lookup_contexts) {
+    if (!isa<InterfaceType>(lookup.context)) {
+      // TODO: Support other kinds of lookup context, notably named
+      // constraints.
+      continue;
+    }
+    const InterfaceType& iface_type = cast<InterfaceType>(*lookup.context);
+    if (std::optional<Nonnull<const Declaration*>> member =
+            FindMember(member_name, iface_type.declaration().members());
+        member.has_value()) {
+      if (found.has_value()) {
+        if (ValueEqual(found->interface, &iface_type)) {
+          continue;
+        }
+        // TODO: If we resolve to the same member either way, this
+        // is not ambiguous.
+        return CompilationError(source_loc)
+               << "ambiguous member access, " << member_name << " found in "
+               << *found->interface << " and " << iface_type;
+      }
+      found = {.interface = &iface_type, .member = member.value()};
+    }
+  }
+
+  if (!found) {
+    if (isa<TypeType>(type)) {
+      return CompilationError(source_loc)
+             << "member access into unconstrained type";
+    }
+    return CompilationError(source_loc)
+           << "member access, " << member_name << " not in " << *type;
+  }
+  return found.value();
+}
+
 auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
                                const ImplScope& impl_scope)
     -> ErrorOr<Success> {
@@ -1224,107 +1436,94 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
                    << access.member();
           }
         }
-        case Value::Kind::TypeOfInterfaceType: {
-          const InterfaceType& iface_type =
-              cast<TypeOfInterfaceType>(object_type).interface_type();
-          if (std::optional<Nonnull<const Declaration*>> member = FindMember(
-                  access.member(), iface_type.declaration().members());
-              member.has_value()) {
-            access.set_static_type(
-                arena_->New<TypeOfMemberName>(Member(*member)));
-            access.set_value_category(ValueCategory::Let);
-            return Success();
+        case Value::Kind::TypeOfInterfaceType:
+        case Value::Kind::TypeOfConstraintType: {
+          const Value* type;
+          if (isa<TypeOfInterfaceType>(object_type)) {
+            type = &cast<TypeOfInterfaceType>(object_type).interface_type();
           } else {
-            return CompilationError(access.source_loc())
-                   << iface_type << " does not have a member named "
-                   << access.member();
+            type = &cast<TypeOfConstraintType>(object_type).constraint_type();
           }
+          CARBON_ASSIGN_OR_RETURN(
+              ConstraintLookupResult result,
+              LookupInConstraint(e->source_loc(), type, access.member()));
+          access.set_found_in_interface(result.interface);
+          access.set_static_type(
+              arena_->New<TypeOfMemberName>(Member(result.member)));
+          access.set_value_category(ValueCategory::Let);
+          return Success();
         }
         case Value::Kind::VariableType: {
           // This case handles access to a method on a receiver whose type
           // is a type variable. For example, `x.foo` where the type of
           // `x` is `T` and `foo` and `T` implements an interface that
           // includes `foo`.
-          const VariableType& var_type = cast<VariableType>(object_type);
-          const Value& typeof_var = var_type.binding().static_type();
-          switch (typeof_var.kind()) {
-            case Value::Kind::InterfaceType: {
-              const auto& iface_type = cast<InterfaceType>(typeof_var);
-              const InterfaceDeclaration& iface_decl = iface_type.declaration();
-              if (std::optional<Nonnull<const Declaration*>> member =
-                      FindMember(access.member(), 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(binding_map, &member_type);
-                access.set_static_type(inst_member_type);
-                CARBON_CHECK(var_type.binding().impl_binding().has_value());
-                access.set_impl(*var_type.binding().impl_binding());
-                return Success();
-              } else {
-                return CompilationError(e->source_loc())
-                       << "member access, " << access.member() << " not in "
-                       << iface_decl.name();
-              }
-              break;
-            }
-            default:
-              return CompilationError(e->source_loc())
-                     << "member access, unexpected " << object_type
-                     << " of non-interface type " << typeof_var << " in " << *e;
-          }
-          break;
+          const Value& typeof_var =
+              cast<VariableType>(object_type).binding().static_type();
+          CARBON_ASSIGN_OR_RETURN(
+              ConstraintLookupResult result,
+              LookupInConstraint(e->source_loc(), &typeof_var,
+                                 access.member()));
+
+          const Value& member_type = result.member->static_type();
+          BindingMap binding_map = result.interface->args();
+          binding_map[result.interface->declaration().self()] = &object_type;
+          Nonnull<const Value*> inst_member_type =
+              Substitute(binding_map, &member_type);
+          access.set_found_in_interface(result.interface);
+          access.set_static_type(inst_member_type);
+
+          CARBON_ASSIGN_OR_RETURN(
+              Nonnull<Expression*> impl,
+              impl_scope.Resolve(result.interface, &object_type,
+                                 e->source_loc(), *this));
+          access.set_impl(impl);
+          return Success();
         }
-        case Value::Kind::InterfaceType: {
+        case Value::Kind::InterfaceType:
+        case Value::Kind::ConstraintType: {
           // This case handles access to a class function from a type variable.
           // If `T` is a type variable and `foo` is a class function in an
           // interface implemented by `T`, then `T.foo` accesses the `foo` class
           // function of `T`.
           CARBON_ASSIGN_OR_RETURN(
-              Nonnull<const Value*> var_addr,
+              Nonnull<const Value*> type,
               InterpExp(&access.object(), arena_, trace_stream_));
-          const VariableType& var_type = cast<VariableType>(*var_addr);
-          const InterfaceType& iface_type = cast<InterfaceType>(object_type);
-          const InterfaceDeclaration& iface_decl = iface_type.declaration();
-          if (std::optional<Nonnull<const Declaration*>> member =
-                  FindMember(access.member(), iface_decl.members());
-              member.has_value()) {
-            CARBON_CHECK(var_type.binding().impl_binding().has_value());
-            access.set_impl(*var_type.binding().impl_binding());
-
-            switch ((*member)->kind()) {
-              case DeclarationKind::FunctionDeclaration: {
-                const auto& func = cast<FunctionDeclaration>(*member);
-                if (func->is_method()) {
-                  break;
-                }
-                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(binding_map, &member_type);
-                access.set_static_type(inst_member_type);
-                return Success();
-              }
-              default:
+          CARBON_ASSIGN_OR_RETURN(
+              ConstraintLookupResult result,
+              LookupInConstraint(e->source_loc(), &object_type,
+                                 access.member()));
+          CARBON_ASSIGN_OR_RETURN(Nonnull<Expression*> impl,
+                                  impl_scope.Resolve(result.interface, type,
+                                                     e->source_loc(), *this));
+          access.set_impl(impl);
+          access.set_found_in_interface(result.interface);
+
+          switch (result.member->kind()) {
+            case DeclarationKind::FunctionDeclaration: {
+              const auto& func = cast<FunctionDeclaration>(*result.member);
+              if (func.is_method()) {
                 break;
+              }
+              const Value& member_type = func.static_type();
+              BindingMap binding_map = result.interface->args();
+              binding_map[result.interface->declaration().self()] = type;
+              Nonnull<const Value*> inst_member_type =
+                  Substitute(binding_map, &member_type);
+              access.set_static_type(inst_member_type);
+              return Success();
             }
-            // TODO: Consider setting the static type of all interface member
-            // declarations and instance member declarations to be member name
-            // types, rather than special-casing member accesses that name
-            // them.
-            access.set_static_type(
-                arena_->New<TypeOfMemberName>(Member(*member)));
-            access.set_value_category(ValueCategory::Let);
-            return Success();
-          } else {
-            return CompilationError(e->source_loc())
-                   << "member access, " << access.member() << " not in "
-                   << iface_decl.name();
+            default:
+              break;
           }
-          break;
+          // TODO: Consider setting the static type of all interface member
+          // declarations and instance member declarations to be member name
+          // types, rather than special-casing member accesses that name
+          // them.
+          access.set_static_type(
+              arena_->New<TypeOfMemberName>(Member(result.member)));
+          access.set_value_category(ValueCategory::Let);
+          return Success();
         }
         default:
           return CompilationError(e->source_loc())
@@ -1549,6 +1748,27 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
           op.set_static_type(arena_->New<PointerType>(ts[0]));
           op.set_value_category(ValueCategory::Let);
           return Success();
+        case Operator::Combine: {
+          std::optional<Nonnull<const ConstraintType*>> constraints[2];
+          for (int i : {0, 1}) {
+            if (auto* iface_type_type = dyn_cast<TypeOfInterfaceType>(ts[i])) {
+              constraints[i] = MakeConstraintForInterface(
+                  e->source_loc(), &iface_type_type->interface_type());
+            } else if (auto* constraint_type_type =
+                           dyn_cast<TypeOfConstraintType>(ts[i])) {
+              constraints[i] = &constraint_type_type->constraint_type();
+            } else {
+              return CompilationError(op.arguments()[i]->source_loc())
+                     << "argument to " << ToString(op.op())
+                     << " should be a constraint, found `" << *ts[i] << "`";
+            }
+          }
+          op.set_static_type(
+              arena_->New<TypeOfConstraintType>(CombineConstraints(
+                  e->source_loc(), {*constraints[0], *constraints[1]})));
+          op.set_value_category(ValueCategory::Let);
+          return Success();
+        }
       }
       break;
     }
@@ -1774,7 +1994,7 @@ void TypeChecker::BringImplIntoScope(Nonnull<const ImplBinding*> impl_binding,
   CARBON_CHECK(impl_binding->type_var()->symbolic_identity().has_value());
   impl_scope.Add(impl_binding->interface(),
                  *impl_binding->type_var()->symbolic_identity(),
-                 CreateImplReference(impl_binding));
+                 CreateImplReference(impl_binding), *this);
 }
 
 auto TypeChecker::TypeCheckTypeExp(Nonnull<Expression*> type_expression,
@@ -1864,7 +2084,7 @@ auto TypeChecker::TypeCheckPattern(
       binding.set_symbolic_identity(val);
       SetValue(&binding, val);
 
-      if (isa<InterfaceType>(type)) {
+      if (isa<InterfaceType, ConstraintType>(type)) {
         Nonnull<ImplBinding*> impl_binding =
             arena_->New<ImplBinding>(binding.source_loc(), &binding, type);
         binding.set_impl_binding(impl_binding);
@@ -2557,7 +2777,7 @@ auto TypeChecker::DeclareImplDeclaration(Nonnull<ImplDeclaration*> impl_decl,
                             impl_decl->deduced_parameters().end());
     scope_info.innermost_non_class_scope->Add(
         iface_type, std::move(deduced_bindings), impl_type_value, impl_bindings,
-        impl_id);
+        impl_id, *this);
   }
 
   // Declare the impl members.
@@ -2676,9 +2896,11 @@ static bool IsValidTypeForAliasTarget(Nonnull<const Value*> type) {
 
     case Value::Kind::FunctionType:
     case Value::Kind::InterfaceType:
+    case Value::Kind::ConstraintType:
     case Value::Kind::TypeType:
     case Value::Kind::TypeOfClassType:
     case Value::Kind::TypeOfInterfaceType:
+    case Value::Kind::TypeOfConstraintType:
     case Value::Kind::TypeOfChoiceType:
     case Value::Kind::TypeOfParameterizedEntityName:
     case Value::Kind::TypeOfMemberName:

+ 31 - 7
explorer/interpreter/type_checker.h

@@ -49,6 +49,13 @@ class TypeChecker {
       Nonnull<const Value*> arg, bool allow_implicit_conversion,
       const ImplScope& impl_scope) const -> ErrorOr<Success>;
 
+  // Construct a type that is the same as `type` except that occurrences
+  // of type variables (aka. `GenericBinding`) are replaced by their
+  // corresponding type in `dict`.
+  auto Substitute(const std::map<Nonnull<const GenericBinding*>,
+                                 Nonnull<const Value*>>& dict,
+                  Nonnull<const Value*> type) const -> Nonnull<const Value*>;
+
   // If `impl` can be an implementation of interface `iface` for the
   // given `type`, then return an expression that will produce the witness
   // for this `impl` (at runtime). Otherwise return std::nullopt.
@@ -57,6 +64,19 @@ class TypeChecker {
                  SourceLocation source_loc) const
       -> std::optional<Nonnull<Expression*>>;
 
+  // Given the witnesses for the components of a constraint, form a witness for
+  // the constraint.
+  auto MakeConstraintWitness(
+      const ConstraintType& constraint,
+      std::vector<Nonnull<Expression*>> impl_constraint_witnesses,
+      SourceLocation source_loc) const -> Nonnull<Expression*>;
+
+  // Given the witnesses for the components of a constraint, form a witness for
+  // the constraint.
+  auto MakeConstraintWitnessAccess(Nonnull<Expression*> witness,
+                                   size_t impl_offset) const
+      -> Nonnull<Expression*>;
+
  private:
   // Information about the currently enclosing scopes.
   struct ScopeInfo {
@@ -305,13 +325,6 @@ class TypeChecker {
                                BuiltinInterfaceName interface) const
       -> ErrorOr<Nonnull<const InterfaceType*>>;
 
-  // Construct a type that is the same as `type` except that occurrences
-  // of type variables (aka. `GenericBinding`) are replaced by their
-  // corresponding type in `dict`.
-  auto Substitute(const std::map<Nonnull<const GenericBinding*>,
-                                 Nonnull<const Value*>>& dict,
-                  Nonnull<const Value*> type) const -> Nonnull<const Value*>;
-
   // 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
@@ -321,6 +334,17 @@ class TypeChecker {
                     const BindingMap& deduced_type_args,
                     ImplExpMap& impls) const -> ErrorOr<Success>;
 
+  // Given an interface type, form a corresponding constraint type.
+  auto MakeConstraintForInterface(SourceLocation source_loc,
+                                  Nonnull<const InterfaceType*> iface_type)
+      -> Nonnull<const ConstraintType*>;
+
+  // Given a list of constraint types, form the combined constraint.
+  auto CombineConstraints(
+      SourceLocation source_loc,
+      llvm::ArrayRef<Nonnull<const ConstraintType*>> constraints)
+      -> Nonnull<const ConstraintType*>;
+
   // Sets value_node.constant_value() to `value`. Can be called multiple
   // times on the same value_node, so long as it is always called with
   // the same value.

+ 108 - 30
explorer/interpreter/value.cpp

@@ -199,6 +199,21 @@ auto Value::SetField(Nonnull<Arena*> arena, const FieldPath& path,
                       field_value, source_loc);
 }
 
+static auto PrintNameWithBindings(llvm::raw_ostream& out,
+                                  Nonnull<const Declaration*> declaration,
+                                  const BindingMap& args) {
+  out << GetName(*declaration).value_or("(anonymous)");
+  // TODO: Print '()' if declaration is parameterized but no args are provided.
+  if (!args.empty()) {
+    out << "(";
+    llvm::ListSeparator sep;
+    for (const auto& [bind, val] : args) {
+      out << sep << bind->name() << " = " << *val;
+    }
+    out << ")";
+  }
+}
+
 void Value::Print(llvm::raw_ostream& out) const {
   switch (kind()) {
     case Value::Kind::AlternativeConstructorValue: {
@@ -319,15 +334,9 @@ void Value::Print(llvm::raw_ostream& out) const {
     }
     case Value::Kind::NominalClassType: {
       const auto& class_type = cast<NominalClassType>(*this);
-      out << "class " << class_type.declaration().name();
-      if (!class_type.type_args().empty()) {
-        out << "(";
-        llvm::ListSeparator sep;
-        for (const auto& [bind, val] : class_type.type_args()) {
-          out << sep << bind->name() << " = " << *val;
-        }
-        out << ")";
-      }
+      out << "class ";
+      PrintNameWithBindings(out, &class_type.declaration(),
+                            class_type.type_args());
       if (!class_type.impls().empty()) {
         out << " impls ";
         llvm::ListSeparator sep;
@@ -346,14 +355,33 @@ 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 << "interface ";
+      PrintNameWithBindings(out, &iface_type.declaration(), iface_type.args());
+      break;
+    }
+    case Value::Kind::ConstraintType: {
+      const auto& constraint = cast<ConstraintType>(*this);
+      out << "constraint ";
+      llvm::ListSeparator combine(" & ");
+      for (const ConstraintType::LookupContext& ctx :
+           constraint.lookup_contexts()) {
+        out << combine << *ctx.context;
+      }
+      out << " where ";
+      llvm::ListSeparator sep;
+      for (const ConstraintType::ImplConstraint& impl :
+           constraint.impl_constraints()) {
+        // TODO: Skip cases where `impl.type` is `.Self` and the interface is
+        // in `lookup_contexts()`.
+        out << sep << *impl.type << " is " << *impl.interface;
+      }
+      for (const ConstraintType::EqualityConstraint& equality :
+           constraint.equality_constraints()) {
+        out << sep;
+        llvm::ListSeparator equal(" == ");
+        for (Nonnull<const Value*> value : equality.values) {
+          out << equal << *value;
         }
-        out << ")";
       }
       break;
     }
@@ -414,6 +442,10 @@ void Value::Print(llvm::raw_ostream& out) const {
                  .name()
           << ")";
       break;
+    case Value::Kind::TypeOfConstraintType:
+      out << "typeof(" << cast<TypeOfConstraintType>(*this).constraint_type()
+          << ")";
+      break;
     case Value::Kind::TypeOfChoiceType:
       out << "typeof(" << cast<TypeOfChoiceType>(*this).choice_type().name()
           << ")";
@@ -472,6 +504,19 @@ void ContinuationValue::StackFragment::Print(llvm::raw_ostream& out) const {
   out << "}";
 }
 
+// Check whether two binding maps, which are assumed to have the same keys, are
+// equal.
+static auto BindingMapEqual(const BindingMap& map1, const BindingMap& map2)
+    -> bool {
+  CARBON_CHECK(map1.size() == map2.size()) << "maps should have same keys";
+  for (const auto& [key, value] : map1) {
+    if (!ValueEqual(value, map2.at(key))) {
+      return false;
+    }
+  }
+  return true;
+}
+
 auto TypeEqual(Nonnull<const Value*> t1, Nonnull<const Value*> t2) -> bool {
   if (t1->kind() != t2->kind()) {
     return false;
@@ -500,30 +545,58 @@ auto TypeEqual(Nonnull<const Value*> t1, Nonnull<const Value*> t2) -> bool {
       }
       return true;
     }
-    case Value::Kind::NominalClassType:
-      if (cast<NominalClassType>(*t1).declaration().name() !=
-          cast<NominalClassType>(*t2).declaration().name()) {
+    case Value::Kind::NominalClassType: {
+      const auto& class1 = cast<NominalClassType>(*t1);
+      const auto& class2 = cast<NominalClassType>(*t2);
+      return class1.declaration().name() == class2.declaration().name() &&
+             BindingMapEqual(class1.type_args(), class2.type_args());
+    }
+    case Value::Kind::InterfaceType: {
+      const auto& iface1 = cast<InterfaceType>(*t1);
+      const auto& iface2 = cast<InterfaceType>(*t2);
+      return iface1.declaration().name() == iface2.declaration().name() &&
+             BindingMapEqual(iface1.args(), iface2.args());
+    }
+    case Value::Kind::ConstraintType: {
+      const auto& constraint1 = cast<ConstraintType>(*t1);
+      const auto& constraint2 = cast<ConstraintType>(*t2);
+      if (constraint1.impl_constraints().size() !=
+              constraint2.impl_constraints().size() ||
+          constraint1.equality_constraints().size() !=
+              constraint2.equality_constraints().size() ||
+          constraint1.lookup_contexts().size() !=
+              constraint2.lookup_contexts().size()) {
         return false;
       }
-      for (const auto& [ty_var1, ty1] :
-           cast<NominalClassType>(*t1).type_args()) {
-        if (!ValueEqual(ty1,
-                        cast<NominalClassType>(*t2).type_args().at(ty_var1))) {
+      for (size_t i = 0; i < constraint1.impl_constraints().size(); ++i) {
+        const auto& impl1 = constraint1.impl_constraints()[i];
+        const auto& impl2 = constraint2.impl_constraints()[i];
+        if (!TypeEqual(impl1.type, impl2.type) ||
+            !TypeEqual(impl1.interface, impl2.interface)) {
           return false;
         }
       }
-      return true;
-    case Value::Kind::InterfaceType:
-      if (cast<InterfaceType>(*t1).declaration().name() !=
-          cast<InterfaceType>(*t2).declaration().name()) {
-        return false;
+      for (size_t i = 0; i < constraint1.equality_constraints().size(); ++i) {
+        const auto& equality1 = constraint1.equality_constraints()[i];
+        const auto& equality2 = constraint2.equality_constraints()[i];
+        if (equality1.values.size() != equality2.values.size()) {
+          return false;
+        }
+        for (size_t j = 0; j < equality1.values.size(); ++j) {
+          if (!ValueEqual(equality1.values[i], equality2.values[i])) {
+            return false;
+          }
+        }
       }
-      for (const auto& [ty_var1, ty1] : cast<InterfaceType>(*t1).args()) {
-        if (!ValueEqual(ty1, cast<InterfaceType>(*t2).args().at(ty_var1))) {
+      for (size_t i = 0; i < constraint1.lookup_contexts().size(); ++i) {
+        const auto& context1 = constraint1.lookup_contexts()[i];
+        const auto& context2 = constraint2.lookup_contexts()[i];
+        if (!TypeEqual(context1.context, context2.context)) {
           return false;
         }
       }
       return true;
+    }
     case Value::Kind::ChoiceType:
       return cast<ChoiceType>(*t1).name() == cast<ChoiceType>(*t2).name();
     case Value::Kind::TupleValue: {
@@ -554,6 +627,9 @@ auto TypeEqual(Nonnull<const Value*> t1, Nonnull<const Value*> t2) -> bool {
     case Value::Kind::TypeOfInterfaceType:
       return TypeEqual(&cast<TypeOfInterfaceType>(*t1).interface_type(),
                        &cast<TypeOfInterfaceType>(*t2).interface_type());
+    case Value::Kind::TypeOfConstraintType:
+      return TypeEqual(&cast<TypeOfConstraintType>(*t1).constraint_type(),
+                       &cast<TypeOfConstraintType>(*t2).constraint_type());
     case Value::Kind::TypeOfChoiceType:
       return TypeEqual(&cast<TypeOfChoiceType>(*t1).choice_type(),
                        &cast<TypeOfChoiceType>(*t2).choice_type());
@@ -671,6 +747,7 @@ auto ValueEqual(Nonnull<const Value*> v1, Nonnull<const Value*> v2) -> bool {
     case Value::Kind::StructType:
     case Value::Kind::NominalClassType:
     case Value::Kind::InterfaceType:
+    case Value::Kind::ConstraintType:
     case Value::Kind::Witness:
     case Value::Kind::ChoiceType:
     case Value::Kind::ContinuationType:
@@ -678,6 +755,7 @@ auto ValueEqual(Nonnull<const Value*> v1, Nonnull<const Value*> v2) -> bool {
     case Value::Kind::StringType:
     case Value::Kind::TypeOfClassType:
     case Value::Kind::TypeOfInterfaceType:
+    case Value::Kind::TypeOfConstraintType:
     case Value::Kind::TypeOfChoiceType:
     case Value::Kind::TypeOfParameterizedEntityName:
     case Value::Kind::TypeOfMemberName:

+ 96 - 9
explorer/interpreter/value.h

@@ -55,6 +55,7 @@ class Value {
     StructType,
     NominalClassType,
     InterfaceType,
+    ConstraintType,
     ChoiceType,
     ContinuationType,  // The type of a continuation.
     VariableType,      // e.g., generic type parameters.
@@ -68,6 +69,7 @@ class Value {
     StringValue,
     TypeOfClassType,
     TypeOfInterfaceType,
+    TypeOfConstraintType,
     TypeOfChoiceType,
     TypeOfParameterizedEntityName,
     TypeOfMemberName,
@@ -134,7 +136,7 @@ class IntValue : public Value {
 };
 
 using ImplWitnessMap =
-    std::map<Nonnull<const ImplBinding*>, Nonnull<const Witness*>>;
+    std::map<Nonnull<const ImplBinding*>, Nonnull<const Value*>>;
 
 // A function value.
 class FunctionValue : public Value {
@@ -160,10 +162,7 @@ class FunctionValue : public Value {
 
   auto type_args() const -> const BindingMap& { return type_args_; }
 
-  auto witnesses() const
-      -> const std::map<Nonnull<const ImplBinding*>, const Witness*>& {
-    return witnesses_;
-  }
+  auto witnesses() const -> const ImplWitnessMap& { return witnesses_; }
 
  private:
   Nonnull<const FunctionDeclaration*> declaration_;
@@ -183,8 +182,7 @@ class BoundMethodValue : public Value {
   explicit BoundMethodValue(Nonnull<const FunctionDeclaration*> declaration,
                             Nonnull<const Value*> receiver,
                             const BindingMap& type_args,
-                            const std::map<Nonnull<const ImplBinding*>,
-                                           Nonnull<const Witness*>>& wits)
+                            const ImplWitnessMap& wits)
       : Value(Kind::BoundMethodValue),
         declaration_(declaration),
         receiver_(receiver),
@@ -581,8 +579,7 @@ class NominalClassType : public Value {
   // run-time type of an object.
   explicit NominalClassType(Nonnull<const ClassDeclaration*> declaration,
                             const BindingMap& type_args,
-                            const std::map<Nonnull<const ImplBinding*>,
-                                           Nonnull<const Witness*>>& wits)
+                            const ImplWitnessMap& wits)
       : Value(Kind::NominalClassType),
         declaration_(declaration),
         type_args_(type_args),
@@ -631,6 +628,7 @@ auto FindMember(const std::string& name,
     -> std::optional<Nonnull<const Declaration*>>;
 
 // An interface type.
+// TODO: Consider removing this once ConstraintType is ready.
 class InterfaceType : public Value {
  public:
   explicit InterfaceType(Nonnull<const InterfaceDeclaration*> declaration)
@@ -674,6 +672,78 @@ class InterfaceType : public Value {
   ImplWitnessMap witnesses_;
 };
 
+// A type-of-type for an unknown constrained type.
+//
+// These types are formed by the `&` operator that combines constraints and by
+// `where` expressions.
+//
+// A constraint has three main properties:
+//
+// * A collection of (type, interface) pairs for interfaces that are known to
+//   be implemented by a type satisfying the constraint.
+// * A collection of sets of values, typically associated constants, that are
+//   known to be the same.
+// * A collection of contexts in which member name lookups will be performed
+//   for a type variable whose type is this constraint.
+//
+// Within these properties, the constrained type can be referred to with a
+// `VariableType` naming the `self_binding`.
+class ConstraintType : public Value {
+ public:
+  // A required implementation of an interface.
+  struct ImplConstraint {
+    Nonnull<const Value*> type;
+    Nonnull<const InterfaceType*> interface;
+  };
+
+  // A collection of values that are known to be the same.
+  struct EqualityConstraint {
+    std::vector<Nonnull<const Value*>> values;
+  };
+
+  // A context in which we might look up a name.
+  struct LookupContext {
+    Nonnull<const Value*> context;
+  };
+
+ public:
+  explicit ConstraintType(Nonnull<const GenericBinding*> self_binding,
+                          std::vector<ImplConstraint> impl_constraints,
+                          std::vector<EqualityConstraint> equality_constraints,
+                          std::vector<LookupContext> lookup_contexts)
+      : Value(Kind::ConstraintType),
+        self_binding_(self_binding),
+        impl_constraints_(std::move(impl_constraints)),
+        equality_constraints_(std::move(equality_constraints)),
+        lookup_contexts_(std::move(lookup_contexts)) {}
+
+  static auto classof(const Value* value) -> bool {
+    return value->kind() == Kind::ConstraintType;
+  }
+
+  auto self_binding() const -> Nonnull<const GenericBinding*> {
+    return self_binding_;
+  }
+
+  auto impl_constraints() const -> llvm::ArrayRef<ImplConstraint> {
+    return impl_constraints_;
+  }
+
+  auto equality_constraints() const -> llvm::ArrayRef<EqualityConstraint> {
+    return equality_constraints_;
+  }
+
+  auto lookup_contexts() const -> llvm::ArrayRef<LookupContext> {
+    return lookup_contexts_;
+  }
+
+ private:
+  Nonnull<const GenericBinding*> self_binding_;
+  std::vector<ImplConstraint> impl_constraints_;
+  std::vector<EqualityConstraint> equality_constraints_;
+  std::vector<LookupContext> lookup_contexts_;
+};
+
 // The witness table for an impl.
 class Witness : public Value {
  public:
@@ -961,6 +1031,23 @@ class TypeOfInterfaceType : public Value {
   Nonnull<const InterfaceType*> iface_type_;
 };
 
+class TypeOfConstraintType : public Value {
+ public:
+  explicit TypeOfConstraintType(Nonnull<const ConstraintType*> constraint_type)
+      : Value(Kind::TypeOfConstraintType), constraint_type_(constraint_type) {}
+
+  static auto classof(const Value* value) -> bool {
+    return value->kind() == Kind::TypeOfConstraintType;
+  }
+
+  auto constraint_type() const -> const ConstraintType& {
+    return *constraint_type_;
+  }
+
+ private:
+  Nonnull<const ConstraintType*> constraint_type_;
+};
+
 // The type of an expression whose value is a choice type. Currently there is no
 // way to explicitly name such a type in Carbon code, but we are tentatively
 // using `typeof(ChoiceName)` as the debug-printing format, in anticipation of

+ 16 - 0
explorer/syntax/parser.ypp

@@ -117,6 +117,8 @@
 %type <Nonnull<Expression*>> primary_expression
 %type <Nonnull<Expression*>> postfix_expression
 %type <Nonnull<Expression*>> ref_deref_expression
+%type <Nonnull<Expression*>> combine_lhs
+%type <Nonnull<Expression*>> combine_expression
 %type <Nonnull<Expression*>> type_expression
 %type <Nonnull<Expression*>> fn_type_expression
 %type <Nonnull<Expression*>> minus_expression
@@ -381,8 +383,21 @@ fn_type_expression:
   FN_TYPE tuple ARROW type_expression
     { $$ = arena->New<FunctionTypeLiteral>(context.source_loc(), $2, $4); }
 ;
+combine_lhs:
+  ref_deref_expression
+| combine_expression
+;
+combine_expression:
+  combine_lhs AMPERSAND ref_deref_expression
+    {
+      $$ = arena->New<PrimitiveOperatorExpression>(
+          context.source_loc(), Operator::Combine,
+          std::vector<Nonnull<Expression*>>({$1, $3}));
+    }
+;
 type_expression:
   ref_deref_expression
+| combine_expression
 | fn_type_expression
 ;
 minus_expression:
@@ -445,6 +460,7 @@ unimpl_expression:
 value_expression:
   // ref_deref_expression excluded due to precedence diamond.
   additive_expression
+| combine_expression
 | fn_type_expression
 | unimpl_expression
 ;

+ 31 - 0
explorer/testdata/constraint/combined_interfaces.carbon

@@ -0,0 +1,31 @@
+// 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: %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+// CHECK: result: 52
+
+package ExplorerTest api;
+
+interface A { fn F() -> i32; fn G() -> i32; }
+interface B { fn H() -> i32; }
+
+fn Get1[T:! A & B](n: T) -> i32 { return n.F() + n.H(); }
+fn Get2[T:! B & A](n: T) -> i32 { return n.G(); }
+
+impl i32 as A {
+  fn F() -> i32 { return 1; }
+  fn G() -> i32 { return 2; }
+}
+impl i32 as B {
+  fn H() -> i32 { return 4; }
+}
+
+fn Main() -> i32 {
+  var z: i32 = 0;
+  return Get1(z) * 10 + Get2(z);
+}

+ 29 - 0
explorer/testdata/constraint/fail_ambiguous_member.carbon

@@ -0,0 +1,29 @@
+// 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} %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{not} %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+
+package ExplorerTest api;
+
+interface A { fn F() -> i32; }
+interface B { fn F() -> i32; }
+
+// CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/constraint/fail_ambiguous_member.carbon:[[@LINE+1]]: ambiguous member access, F found in interface A and interface B
+fn Get[T:! A & B](n: T) -> i32 { return n.F(); }
+
+impl i32 as A {
+  fn F() -> i32 { return 1; }
+}
+impl i32 as B {
+  fn F() -> i32 { return 2; }
+}
+
+fn Main() -> i32 {
+  var z: i32 = 0;
+  return Get(z);
+}

+ 29 - 0
explorer/testdata/constraint/missing_member.carbon

@@ -0,0 +1,29 @@
+// 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} %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{not} %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+
+package ExplorerTest api;
+
+interface A { fn F() -> i32; }
+interface B { fn G() -> i32; }
+
+// CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/constraint/missing_member.carbon:[[@LINE+1]]: member access, H not in constraint interface A & interface B where .Self:! Type is interface A, .Self:! Type is interface B
+fn Get[T:! A & B](n: T) -> i32 { return n.H(); }
+
+impl i32 as A {
+  fn F() -> i32 { return 1; }
+}
+impl i32 as B {
+  fn G() -> i32 { return 2; }
+}
+
+fn Main() -> i32 {
+  var z: i32 = 0;
+  return Get(z);
+}

+ 1 - 1
explorer/testdata/generic_class/fail_field_access_on_generic.carbon

@@ -11,7 +11,7 @@
 package ExplorerTest api;
 
 fn BadSimpleMemberAccess[T:! Type](a: T) -> T {
-  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/generic_class/fail_field_access_on_generic.carbon:[[@LINE+1]]: member access, unexpected T:! Type of non-interface type Type in a.x
+  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/generic_class/fail_field_access_on_generic.carbon:[[@LINE+1]]: member access into unconstrained type
   return a.x;
 }
 

+ 1 - 1
explorer/testdata/interface/fail_interface_missing_member.carbon

@@ -15,7 +15,7 @@ interface Vector {
 }
 
 fn ScaleGeneric[T:! Vector](a: T, s: i32) -> T {
-  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/interface/fail_interface_missing_member.carbon:[[@LINE+1]]: member access, Scale not in Vector
+  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/interface/fail_interface_missing_member.carbon:[[@LINE+1]]: member access, Scale not in interface Vector
   return a.Scale(s);
 }
 

+ 33 - 0
explorer/testdata/member_access/qualified_constraint_member.carbon

@@ -0,0 +1,33 @@
+// 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: %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+// CHECK: result: 12
+
+package Foo api;
+interface A {
+  fn F[me: Self]() -> i32;
+}
+interface B {
+  fn G(o: Self) -> i32;
+}
+alias C = A & B;
+class X {
+  impl as A {
+    fn F[me: Self]() -> i32 { return 10 * me.n; }
+  }
+  impl as B {
+    fn G(o: Self) -> i32 { return o.n; }
+  }
+  var n: i32;
+}
+fn Main() -> i32 {
+  var v: X = {.n = 1};
+  var w: X = {.n = 2};
+  return v.(C.F)() + X.(C.G)(w);
+}