Explorar o código

Initial support for associated constants (#1376)

Basic support for declaring, specifying the values of, and using associated constants.

This is incomplete in various ways. For example, when checking whether a type satisfies a constraint, there is no check that its associated constants match those in the constraint, and name lookup into a value whose type is an associated constant is not supported yet.

Co-authored-by: Jon Ross-Perkins <jperkins@google.com>
Richard Smith %!s(int64=3) %!d(string=hai) anos
pai
achega
663ed32b1b
Modificáronse 40 ficheiros con 1313 adicións e 270 borrados
  1. 7 0
      common/fuzzing/carbon.proto
  2. 16 0
      common/fuzzing/proto_to_carbon.cpp
  3. 1 0
      explorer/ast/ast_rtti.txt
  4. 13 0
      explorer/ast/declaration.cpp
  5. 20 0
      explorer/ast/declaration.h
  6. 7 0
      explorer/fuzzing/ast_to_proto.cpp
  7. 10 2
      explorer/interpreter/field_path.h
  8. 36 4
      explorer/interpreter/impl_scope.cpp
  9. 20 2
      explorer/interpreter/impl_scope.h
  10. 101 10
      explorer/interpreter/interpreter.cpp
  11. 1 0
      explorer/interpreter/resolve_control_flow.cpp
  12. 13 0
      explorer/interpreter/resolve_names.cpp
  13. 345 183
      explorer/interpreter/type_checker.cpp
  14. 29 11
      explorer/interpreter/type_checker.h
  15. 148 33
      explorer/interpreter/value.cpp
  16. 92 7
      explorer/interpreter/value.h
  17. 6 0
      explorer/syntax/parser.ypp
  18. 18 0
      explorer/testdata/assoc_const/fail_anonymous.carbon
  19. 22 0
      explorer/testdata/assoc_const/fail_incomplete_impl_1.carbon
  20. 22 0
      explorer/testdata/assoc_const/fail_incomplete_impl_2.carbon
  21. 36 0
      explorer/testdata/assoc_const/fail_indirectly_equal.carbon
  22. 38 0
      explorer/testdata/assoc_const/fail_match_in_deduction.carbon
  23. 46 0
      explorer/testdata/assoc_const/fail_multi_impl_scoping.carbon
  24. 27 0
      explorer/testdata/assoc_const/fail_multiple_deduction.carbon
  25. 20 0
      explorer/testdata/assoc_const/fail_overspecified_impl.carbon
  26. 19 0
      explorer/testdata/assoc_const/fail_redefined.carbon
  27. 27 0
      explorer/testdata/assoc_const/fail_unknown_value.carbon
  28. 28 0
      explorer/testdata/assoc_const/fail_unknown_value_specified_in_constraint.carbon
  29. 40 0
      explorer/testdata/assoc_const/impl_lookup.carbon
  30. 27 0
      explorer/testdata/assoc_const/implement.carbon
  31. 30 0
      explorer/testdata/assoc_const/simple_constraint.carbon
  32. 30 0
      explorer/testdata/assoc_const/simple_equality.carbon
  33. 3 3
      explorer/testdata/constraint/fail_where_equals_different_types.carbon
  34. 1 1
      explorer/testdata/constraint/missing_member.carbon
  35. 1 1
      explorer/testdata/constraint/no_combine_equality.carbon
  36. 3 3
      explorer/testdata/generic_class/fail_argument_deduction.carbon
  37. 1 1
      explorer/testdata/generic_class/fail_field_access_on_generic.carbon
  38. 3 3
      explorer/testdata/generic_function/fail_type_deduction_mismatch.carbon
  39. 5 5
      explorer/testdata/impl/impl_constraint.carbon
  40. 1 1
      explorer/testdata/interface/fail_bad_member_kind.carbon

+ 7 - 0
common/fuzzing/carbon.proto

@@ -353,6 +353,12 @@ message VariableDeclaration {
   optional Expression initializer = 2;
 }
 
+message LetDeclaration {
+  optional Pattern pattern = 1;
+  // TODO: Add `optional Expression initializer = 2;` once explorer supports
+  // `let` declarations in general.
+}
+
 message InterfaceDeclaration {
   optional string name = 1;
   repeated Declaration members = 2;
@@ -386,6 +392,7 @@ message Declaration {
     InterfaceDeclaration interface = 5;
     ImplDeclaration impl = 6;
     AliasDeclaration alias = 7;
+    LetDeclaration let = 8;
   }
 }
 

+ 16 - 0
common/fuzzing/proto_to_carbon.cpp

@@ -721,6 +721,22 @@ static auto DeclarationToCarbon(const Fuzzing::Declaration& declaration,
       break;
     }
 
+    case Fuzzing::Declaration::kLet: {
+      const auto& let = declaration.let();
+      out << "let ";
+      PatternToCarbon(let.pattern(), out);
+
+      // TODO: Print out the initializer once it's supported.
+      /*
+      if (let.has_initializer()) {
+        out << " = ";
+        ExpressionToCarbon(let.initializer(), out);
+      }
+      */
+      out << ";";
+      break;
+    }
+
     case Fuzzing::Declaration::kInterface: {
       const auto& interface = declaration.interface();
       out << "interface ";

+ 1 - 0
explorer/ast/ast_rtti.txt

@@ -19,6 +19,7 @@ abstract class Declaration : AstNode;
   class ChoiceDeclaration : Declaration;
   class VariableDeclaration : Declaration;
   class InterfaceDeclaration : Declaration;
+  class AssociatedConstantDeclaration : Declaration;
   class ImplDeclaration : Declaration;
   class AliasDeclaration : Declaration;
 class ImplBinding : AstNode;

+ 13 - 0
explorer/ast/declaration.cpp

@@ -74,6 +74,11 @@ void Declaration::Print(llvm::raw_ostream& out) const {
       break;
     }
 
+    case DeclarationKind::AssociatedConstantDeclaration:
+      PrintID(out);
+      out << ";\n";
+      break;
+
     case DeclarationKind::SelfDeclaration: {
       out << "Self";
       break;
@@ -130,6 +135,12 @@ void Declaration::PrintID(llvm::raw_ostream& out) const {
       break;
     }
 
+    case DeclarationKind::AssociatedConstantDeclaration: {
+      const auto& let = cast<AssociatedConstantDeclaration>(*this);
+      out << "let " << let.binding();
+      break;
+    }
+
     case DeclarationKind::SelfDeclaration: {
       out << "Self";
       break;
@@ -156,6 +167,8 @@ auto GetName(const Declaration& declaration)
       return cast<InterfaceDeclaration>(declaration).name();
     case DeclarationKind::VariableDeclaration:
       return cast<VariableDeclaration>(declaration).binding().name();
+    case DeclarationKind::AssociatedConstantDeclaration:
+      return cast<AssociatedConstantDeclaration>(declaration).binding().name();
     case DeclarationKind::ImplDeclaration:
       return std::nullopt;
     case DeclarationKind::SelfDeclaration:

+ 20 - 0
explorer/ast/declaration.h

@@ -351,6 +351,26 @@ class InterfaceDeclaration : public Declaration {
   std::vector<Nonnull<Declaration*>> members_;
 };
 
+class AssociatedConstantDeclaration : public Declaration {
+ public:
+  AssociatedConstantDeclaration(SourceLocation source_loc,
+                                Nonnull<GenericBinding*> binding)
+      : Declaration(AstNodeKind::AssociatedConstantDeclaration, source_loc),
+        binding_(binding) {}
+
+  static auto classof(const AstNode* node) -> bool {
+    return InheritsFromAssociatedConstantDeclaration(node->kind());
+  }
+
+  auto binding() const -> const GenericBinding& { return *binding_; }
+  auto binding() -> GenericBinding& { return *binding_; }
+
+  auto value_category() const -> ValueCategory { return ValueCategory::Let; }
+
+ private:
+  Nonnull<GenericBinding*> binding_;
+};
+
 enum class ImplKind { InternalImpl, ExternalImpl };
 
 class ImplDeclaration : public Declaration {

+ 7 - 0
explorer/fuzzing/ast_to_proto.cpp

@@ -609,6 +609,13 @@ static auto DeclarationToProto(const Declaration& declaration)
       break;
     }
 
+    case DeclarationKind::AssociatedConstantDeclaration: {
+      const auto& assoc = cast<AssociatedConstantDeclaration>(declaration);
+      auto* let_proto = declaration_proto.mutable_let();
+      *let_proto->mutable_pattern() = PatternToProto(assoc.binding());
+      break;
+    }
+
     case DeclarationKind::InterfaceDeclaration: {
       const auto& interface = cast<InterfaceDeclaration>(declaration);
       auto* interface_proto = declaration_proto.mutable_interface();

+ 10 - 2
explorer/interpreter/field_path.h

@@ -15,6 +15,7 @@
 
 namespace Carbon {
 
+class InterfaceType;
 class Witness;
 
 // Given some initial Value, a FieldPath identifies a sub-Value within it,
@@ -40,13 +41,19 @@ class FieldPath {
   class Component {
    public:
     explicit Component(Member member) : member_(member) {}
-    Component(Member member, std::optional<Nonnull<const Witness*>> witness)
-        : member_(member), witness_(witness) {}
+    Component(Member member,
+              std::optional<Nonnull<const InterfaceType*>> interface,
+              std::optional<Nonnull<const Witness*>> witness)
+        : member_(member), interface_(interface), witness_(witness) {}
 
     auto member() const -> Member { return member_; }
 
     auto name() const -> std::string_view { return member_.name(); }
 
+    auto interface() const -> std::optional<Nonnull<const InterfaceType*>> {
+      return interface_;
+    }
+
     auto witness() const -> std::optional<Nonnull<const Witness*>> {
       return witness_;
     }
@@ -55,6 +62,7 @@ class FieldPath {
 
    private:
     Member member_;
+    std::optional<Nonnull<const InterfaceType*>> interface_;
     std::optional<Nonnull<const Witness*>> witness_;
   };
 

+ 36 - 4
explorer/interpreter/impl_scope.cpp

@@ -26,15 +26,24 @@ void ImplScope::Add(Nonnull<const Value*> iface,
                     llvm::ArrayRef<Nonnull<const ImplBinding*>> impl_bindings,
                     Nonnull<Expression*> impl_expr,
                     const TypeChecker& type_checker) {
-  if (auto* constraint = dyn_cast<ConstraintType>(iface)) {
+  if (auto* orig_constraint = dyn_cast<ConstraintType>(iface)) {
     BindingMap map;
-    map[constraint->self_binding()] = type;
+    map[orig_constraint->self_binding()] = type;
+    const ConstraintType* constraint =
+        cast<ConstraintType>(type_checker.Substitute(map, orig_constraint));
     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,
+      Add(impl.interface, deduced, impl.type, impl_bindings,
           type_checker.MakeConstraintWitnessAccess(impl_expr, i), type_checker);
     }
+    // A paremterized impl declaration doesn't contribute any equality
+    // constraints to the scope. Instead, we'll resolve the equality
+    // constraints by resolving a witness when needed.
+    if (deduced.empty()) {
+      for (auto& equality_constraint : constraint->equality_constraints()) {
+        equalities_.push_back(&equality_constraint);
+      }
+    }
     return;
   }
 
@@ -77,6 +86,22 @@ auto ImplScope::Resolve(Nonnull<const Value*> constraint_type,
   CARBON_FATAL() << "expected a constraint, not " << *constraint_type;
 }
 
+auto ImplScope::VisitEqualValues(
+    Nonnull<const Value*> value,
+    llvm::function_ref<bool(Nonnull<const Value*>)> visitor) const -> bool {
+  for (Nonnull<const ConstraintType::EqualityConstraint*> eq : equalities_) {
+    if (!eq->VisitEqualValues(value, visitor)) {
+      return false;
+    }
+  }
+  for (Nonnull<const ImplScope*> parent : parent_scopes_) {
+    if (!parent->VisitEqualValues(value, visitor)) {
+      return false;
+    }
+  }
+  return true;
+}
+
 auto ImplScope::ResolveInterface(Nonnull<const InterfaceType*> iface_type,
                                  Nonnull<const Value*> type,
                                  SourceLocation source_loc,
@@ -147,6 +172,13 @@ void ImplScope::Print(llvm::raw_ostream& out) const {
   for (const Impl& impl : impls_) {
     out << sep << *(impl.type) << " as " << *(impl.interface);
   }
+  for (Nonnull<const ConstraintType::EqualityConstraint*> eq : equalities_) {
+    out << sep;
+    llvm::ListSeparator equal(" == ");
+    for (Nonnull<const Value*> value : eq->values) {
+      out << equal << *value;
+    }
+  }
   out << "\n";
   for (const Nonnull<const ImplScope*>& parent : parent_scopes_) {
     out << *parent;

+ 20 - 2
explorer/interpreter/impl_scope.h

@@ -6,12 +6,11 @@
 #define CARBON_EXPLORER_INTERPRETER_IMPL_SCOPE_H_
 
 #include "explorer/ast/declaration.h"
+#include "explorer/interpreter/value.h"
 
 namespace Carbon {
 
-class Value;
 class TypeChecker;
-class InterfaceType;
 
 // The `ImplScope` class is responsible for mapping a type and
 // interface to the location of the witness table for the `impl` for
@@ -38,6 +37,9 @@ class InterfaceType;
 //  impl is visible in the body of `bar`. In contrast, the call to
 //  `x.foo` in `baz` is not valid because there is no visible impl for
 //  `U` and `Fooable` in that scope.
+//
+// `ImplScope` also tracks the type equalities that are known in a particular
+// scope.
 class ImplScope {
  public:
   // Associates `iface` and `type` with the `impl` in this scope.
@@ -51,6 +53,11 @@ class ImplScope {
            llvm::ArrayRef<Nonnull<const ImplBinding*>> impl_bindings,
            Nonnull<Expression*> impl, const TypeChecker& type_checker);
 
+  // Add a type equality constraint.
+  void AddEqualityConstraint(Nonnull<const EqualityConstraint*> equal) {
+    equalities_.push_back(equal);
+  }
+
   // Make `parent` a parent of this scope.
   // REQUIRES: `parent` is not already a parent of this scope.
   void AddParent(Nonnull<const ImplScope*> parent);
@@ -62,6 +69,16 @@ class ImplScope {
                SourceLocation source_loc, const TypeChecker& type_checker) const
       -> ErrorOr<Nonnull<Expression*>>;
 
+  // Visits the values that are a single step away from `value` according to an
+  // equality constraint that is in scope. That is, the values `v` such that we
+  // have a `value == v` equality constraint in scope.
+  //
+  // Stops and returns `false` if any call to the visitor returns `false`,
+  // otherwise returns `true`.
+  auto VisitEqualValues(
+      Nonnull<const Value*> value,
+      llvm::function_ref<bool(Nonnull<const Value*>)> visitor) const -> bool;
+
   void Print(llvm::raw_ostream& out) const;
 
   // The `Impl` struct is a key-value pair where the key is the
@@ -114,6 +131,7 @@ class ImplScope {
       -> ErrorOr<std::optional<Nonnull<Expression*>>>;
 
   std::vector<Impl> impls_;
+  std::vector<Nonnull<const EqualityConstraint*>> equalities_;
   std::vector<Nonnull<const ImplScope*>> parent_scopes_;
 };
 

+ 101 - 10
explorer/interpreter/interpreter.cpp

@@ -99,6 +99,23 @@ class Interpreter {
   auto EvalExpRecursively(Nonnull<const Expression*> exp)
       -> ErrorOr<Nonnull<const Value*>>;
 
+  // Evaluate an associated constant by evaluating its witness and looking
+  // inside the impl for the corresponding value.
+  //
+  // TODO: This approach doesn't provide values that are known because they
+  // appear in constraints:
+  //
+  //   interface Iface { let N:! i32; }
+  //   fn PickType(N: i32) -> Type { return i32; }
+  //   fn F[T:! Iface where .N == 5](x: T) {
+  //     var x: PickType(T.N) = 0;
+  //   }
+  //
+  // ... will fail because we can't resolve T.N to 5 at compile time.
+  auto EvalAssociatedConstant(Nonnull<const AssociatedConstant*> assoc,
+                              SourceLocation source_loc)
+      -> ErrorOr<Nonnull<const Value*>>;
+
   // Instantiate a type by replacing all type variables that occur inside the
   // type by the current values of those variables.
   //
@@ -181,7 +198,7 @@ auto Interpreter::EvalPrim(Operator op, Nonnull<const Value*> static_type,
       return arena_->New<BoolValue>(cast<BoolValue>(*args[0]).value() ||
                                     cast<BoolValue>(*args[1]).value());
     case Operator::Eq:
-      return arena_->New<BoolValue>(ValueEqual(args[0], args[1]));
+      return arena_->New<BoolValue>(ValueEqual(args[0], args[1], std::nullopt));
     case Operator::Ptr:
       return arena_->New<PointerType>(args[0]);
     case Operator::Deref:
@@ -309,7 +326,7 @@ auto PatternMatch(Nonnull<const Value*> p, Nonnull<const Value*> v,
       // on the typechecker to ensure that `v` is a type.
       return true;
     default:
-      return ValueEqual(p, v);
+      return ValueEqual(p, v, std::nullopt);
   }
 }
 
@@ -454,6 +471,51 @@ auto Interpreter::EvalExpRecursively(Nonnull<const Expression*> exp)
   return result;
 }
 
+auto Interpreter::EvalAssociatedConstant(
+    Nonnull<const AssociatedConstant*> assoc, SourceLocation source_loc)
+    -> ErrorOr<Nonnull<const Value*>> {
+  // Find the witness.
+  Nonnull<const Value*> witness = &assoc->witness();
+  if (auto* sym = dyn_cast<SymbolicWitness>(witness)) {
+    CARBON_ASSIGN_OR_RETURN(witness,
+                            EvalExpRecursively(&sym->impl_expression()));
+  }
+  if (!isa<ImplWitness>(witness)) {
+    CARBON_CHECK(phase() == Phase::CompileTime)
+        << "symbolic witnesses should only be formed at compile time";
+    return CompilationError(source_loc)
+           << "value of associated constant " << *assoc << " is not known";
+  }
+
+  auto& impl_witness = cast<ImplWitness>(*witness);
+  Nonnull<const ConstraintType*> constraint =
+      impl_witness.declaration().constraint_type();
+  Nonnull<const Value*> expected = arena_->New<AssociatedConstant>(
+      &constraint->self_binding()->value(), &assoc->interface(),
+      &assoc->constant(), &impl_witness);
+  std::optional<Nonnull<const Value*>> result;
+  constraint->VisitEqualValues(expected,
+                               [&](Nonnull<const Value*> equal_value) {
+                                 // TODO: The value might depend on the
+                                 // parameters of the impl. We need to
+                                 // substitute impl_witness.type_args() into the
+                                 // value.
+                                 if (isa<AssociatedConstant>(equal_value)) {
+                                   return true;
+                                 }
+                                 // TODO: This makes an arbitrary choice if
+                                 // there's more than one equal value. It's not
+                                 // clear how to handle that case.
+                                 result = equal_value;
+                                 return false;
+                               });
+  if (!result) {
+    CARBON_FATAL() << impl_witness.declaration()
+                   << " is missing value for associated constant " << *assoc;
+  }
+  return *result;
+}
+
 auto Interpreter::InstantiateType(Nonnull<const Value*> type,
                                   SourceLocation source_loc)
     -> ErrorOr<Nonnull<const Value*>> {
@@ -475,6 +537,12 @@ auto Interpreter::InstantiateType(Nonnull<const Value*> type,
           InstantiateBindings(&class_type.bindings(), source_loc));
       return arena_->New<NominalClassType>(&class_type.declaration(), bindings);
     }
+    case Value::Kind::AssociatedConstant: {
+      CARBON_ASSIGN_OR_RETURN(
+          Nonnull<const Value*> type_value,
+          EvalAssociatedConstant(cast<AssociatedConstant>(type), source_loc));
+      return InstantiateType(type_value, source_loc);
+    }
     default:
       return type;
   }
@@ -566,7 +634,7 @@ auto Interpreter::Convert(Nonnull<const Value*> value,
           return arena_->New<StructValue>(std::move(new_elements));
         }
         case Value::Kind::NominalClassType: {
-          // Instantiate the `destintation_type` to obtain the runtime
+          // Instantiate the `destination_type` to obtain the runtime
           // type of the object.
           CARBON_ASSIGN_OR_RETURN(
               Nonnull<const Value*> inst_dest,
@@ -621,6 +689,12 @@ auto Interpreter::Convert(Nonnull<const Value*> value,
       }
       return arena_->New<TupleValue>(std::move(new_elements));
     }
+    case Value::Kind::AssociatedConstant: {
+      CARBON_ASSIGN_OR_RETURN(
+          Nonnull<const Value*> value,
+          EvalAssociatedConstant(cast<AssociatedConstant>(value), source_loc));
+      return Convert(value, destination_type, source_loc);
+    }
   }
 }
 
@@ -854,6 +928,14 @@ auto Interpreter::StepExp() -> ErrorOr<Success> {
             std::make_unique<ExpressionAction>(access.impl().value()));
       } else {
         // Finally, produce the result.
+        std::optional<Nonnull<const InterfaceType*>> found_in_interface =
+            access.found_in_interface();
+        if (found_in_interface) {
+          CARBON_ASSIGN_OR_RETURN(
+              Nonnull<const Value*> instantiated,
+              InstantiateType(*found_in_interface, exp.source_loc()));
+          found_in_interface = cast<InterfaceType>(instantiated);
+        }
         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
@@ -864,9 +946,8 @@ auto Interpreter::StepExp() -> ErrorOr<Success> {
           if (!isa<InterfaceType, ConstraintType>(act.results()[0])) {
             type_result = act.results()[0];
           }
-          MemberName* member_name =
-              arena_->New<MemberName>(type_result, access.found_in_interface(),
-                                      member_name_type->member());
+          MemberName* member_name = arena_->New<MemberName>(
+              type_result, 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
@@ -875,7 +956,8 @@ auto Interpreter::StepExp() -> ErrorOr<Success> {
           if (access.impl().has_value()) {
             witness = cast<Witness>(act.results()[1]);
           }
-          FieldPath::Component member(access.member(), witness);
+          FieldPath::Component member(access.member(), found_in_interface,
+                                      witness);
           const Value* aggregate;
           if (const auto* lvalue = dyn_cast<LValue>(act.results()[0])) {
             CARBON_ASSIGN_OR_RETURN(
@@ -907,6 +989,14 @@ auto Interpreter::StepExp() -> ErrorOr<Success> {
             std::make_unique<ExpressionAction>(access.impl().value()));
       } else {
         // Finally, produce the result.
+        std::optional<Nonnull<const InterfaceType*>> found_in_interface =
+            access.member().interface();
+        if (found_in_interface) {
+          CARBON_ASSIGN_OR_RETURN(
+              Nonnull<const Value*> instantiated,
+              InstantiateType(*found_in_interface, exp.source_loc()));
+          found_in_interface = cast<InterfaceType>(instantiated);
+        }
         if (forming_member_name) {
           // If we're forming a member name, we must be in the outer evaluation
           // in `Type.(Interface.method)`. Produce the same method name with
@@ -917,8 +1007,7 @@ auto Interpreter::StepExp() -> ErrorOr<Success> {
               << "compound member access forming a member name should be "
                  "performing impl lookup";
           auto* member_name = arena_->New<MemberName>(
-              act.results()[0], access.member().interface(),
-              access.member().member());
+              act.results()[0], found_in_interface, access.member().member());
           return todo_.FinishAction(member_name);
         } else {
           // Access the object to find the named member.
@@ -933,7 +1022,8 @@ auto Interpreter::StepExp() -> ErrorOr<Success> {
                 object, Convert(object, *access.member().base_type(),
                                 exp.source_loc()));
           }
-          FieldPath::Component field(access.member().member(), witness);
+          FieldPath::Component field(access.member().member(),
+                                     found_in_interface, witness);
           CARBON_ASSIGN_OR_RETURN(Nonnull<const Value*> member,
                                   object->GetMember(arena_, FieldPath(field),
                                                     exp.source_loc(), object));
@@ -1504,6 +1594,7 @@ auto Interpreter::StepDeclaration() -> ErrorOr<Success> {
     case DeclarationKind::ClassDeclaration:
     case DeclarationKind::ChoiceDeclaration:
     case DeclarationKind::InterfaceDeclaration:
+    case DeclarationKind::AssociatedConstantDeclaration:
     case DeclarationKind::ImplDeclaration:
     case DeclarationKind::SelfDeclaration:
     case DeclarationKind::AliasDeclaration:

+ 1 - 0
explorer/interpreter/resolve_control_flow.cpp

@@ -165,6 +165,7 @@ auto ResolveControlFlow(Nonnull<Declaration*> declaration) -> ErrorOr<Success> {
     }
     case DeclarationKind::ChoiceDeclaration:
     case DeclarationKind::VariableDeclaration:
+    case DeclarationKind::AssociatedConstantDeclaration:
     case DeclarationKind::SelfDeclaration:
     case DeclarationKind::AliasDeclaration:
       // do nothing

+ 13 - 0
explorer/interpreter/resolve_names.cpp

@@ -62,6 +62,14 @@ static auto AddExposedNames(const Declaration& declaration,
       }
       break;
     }
+    case DeclarationKind::AssociatedConstantDeclaration: {
+      auto& let = cast<AssociatedConstantDeclaration>(declaration);
+      if (let.binding().name() != AnonymousName) {
+        CARBON_RETURN_IF_ERROR(
+            enclosing_scope.Add(let.binding().name(), &let.binding()));
+      }
+      break;
+    }
     case DeclarationKind::SelfDeclaration: {
       auto& self = cast<SelfDeclaration>(declaration);
       CARBON_RETURN_IF_ERROR(enclosing_scope.Add("Self", &self));
@@ -566,6 +574,11 @@ static auto ResolveNames(Declaration& declaration, StaticScope& enclosing_scope,
       }
       break;
     }
+    case DeclarationKind::AssociatedConstantDeclaration: {
+      auto& let = cast<AssociatedConstantDeclaration>(declaration);
+      CARBON_RETURN_IF_ERROR(ResolveNames(let.binding(), enclosing_scope));
+      break;
+    }
 
     case DeclarationKind::SelfDeclaration: {
       CARBON_FATAL() << "Unreachable: resolving names for `Self` declaration";

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 345 - 183
explorer/interpreter/type_checker.cpp


+ 29 - 11
explorer/interpreter/type_checker.h

@@ -78,6 +78,8 @@ class TypeChecker {
       -> Nonnull<Expression*>;
 
  private:
+  struct SingleStepEqualityContext;
+
   // Information about the currently enclosing scopes.
   struct ScopeInfo {
     static auto ForNonClassScope(Nonnull<ImplScope*> impl_scope) -> ScopeInfo {
@@ -194,7 +196,8 @@ class TypeChecker {
   // interface is present in the given `impl`.
   auto CheckImplIsComplete(Nonnull<const InterfaceType*> iface_type,
                            Nonnull<const ImplDeclaration*> impl_decl,
-                           Nonnull<const Value*> self_type) -> ErrorOr<Success>;
+                           Nonnull<const Value*> self_type,
+                           const ImplScope& impl_scope) -> ErrorOr<Success>;
 
   // Check that an `impl` declaration satisfies its constraints and add the
   // corresponding `ImplBinding`s to the impl scope.
@@ -261,6 +264,12 @@ class TypeChecker {
                                      const ImplScope& impl_scope)
       -> ErrorOr<Success>;
 
+  // Bring the associated constants in `constraint` that constrain the
+  // implementation of `interface` for `self` into `scope`.
+  void BringAssociatedConstantsIntoScope(
+      Nonnull<const ConstraintType*> constraint, Nonnull<const Value*> self,
+      Nonnull<const InterfaceType*> interface, ImplScope& scope);
+
   // Type check all the members of the implementation.
   auto TypeCheckImplDeclaration(Nonnull<ImplDeclaration*> impl_decl,
                                 const ImplScope& impl_scope)
@@ -296,13 +305,20 @@ class TypeChecker {
   // must be types.
   auto FieldTypesImplicitlyConvertible(
       llvm::ArrayRef<NamedValue> source_fields,
-      llvm::ArrayRef<NamedValue> destination_fields) const -> bool;
+      llvm::ArrayRef<NamedValue> destination_fields,
+      const ImplScope& impl_scope) const -> bool;
 
   // Returns true if *source is implicitly convertible to *destination. *source
   // and *destination must be concrete types.
-  auto IsImplicitlyConvertible(
-      Nonnull<const Value*> source, Nonnull<const Value*> destination,
-      std::optional<Nonnull<const ImplScope*>> impl_scope) const -> bool;
+  //
+  // If allow_user_defined_conversions, conversions requiring a user-defined
+  // `ImplicitAs` implementation are not considered, and only builtin
+  // conversions will be allowed.
+  auto IsImplicitlyConvertible(Nonnull<const Value*> source,
+                               Nonnull<const Value*> destination,
+                               const ImplScope& impl_scope,
+                               bool allow_user_defined_conversions) const
+      -> bool;
 
   // Attempt to implicitly convert type-checked expression `source` to the type
   // `destination`.
@@ -315,16 +331,18 @@ class TypeChecker {
   // Check whether `actual` is implicitly convertible to `expected`
   // and halt with a fatal compilation error if it is not.
   //
-  // If `impl_scope` is `std::nullopt`, only built-in conversions are
-  // considered.
-  // TODO: Remove this behavior.
-  //
   // TODO: Does not actually perform the conversion if a user-defined
   // conversion is needed. Should be used very rarely for that reason.
   auto ExpectType(SourceLocation source_loc, const std::string& context,
                   Nonnull<const Value*> expected, Nonnull<const Value*> actual,
-                  std::optional<Nonnull<const ImplScope*>> impl_scope) const
-      -> ErrorOr<Success>;
+                  const ImplScope& impl_scope) const -> ErrorOr<Success>;
+
+  // Check whether `actual` is the same type as `expected` and halt with a
+  // fatal compilation error if it is not.
+  auto ExpectExactType(SourceLocation source_loc, const std::string& context,
+                       Nonnull<const Value*> expected,
+                       Nonnull<const Value*> actual,
+                       const ImplScope& impl_scope) const -> ErrorOr<Success>;
 
   // The name of a builtin interface, with any arguments.
   struct BuiltinInterfaceName {

+ 148 - 33
explorer/interpreter/value.cpp

@@ -10,6 +10,7 @@
 #include "explorer/common/arena.h"
 #include "explorer/common/error_builders.h"
 #include "explorer/interpreter/action.h"
+#include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/StringExtras.h"
 #include "llvm/Support/Casting.h"
 #include "llvm/Support/Error.h"
@@ -18,6 +19,8 @@ namespace Carbon {
 
 using llvm::cast;
 using llvm::dyn_cast;
+using llvm::dyn_cast_or_null;
+using llvm::isa;
 
 auto StructValue::FindField(std::string_view name) const
     -> std::optional<Nonnull<const Value*>> {
@@ -37,6 +40,16 @@ static auto GetMember(Nonnull<Arena*> arena, Nonnull<const Value*> v,
 
   if (field.witness().has_value()) {
     Nonnull<const Witness*> witness = cast<Witness>(*field.witness());
+
+    // Associated constants.
+    if (auto* assoc_const = dyn_cast_or_null<AssociatedConstantDeclaration>(
+            field.member().declaration().value_or(nullptr))) {
+      CARBON_CHECK(field.interface()) << "have witness but no interface";
+      return arena->New<AssociatedConstant>(v, *field.interface(), assoc_const,
+                                            witness);
+    }
+
+    // Associated functions.
     switch (witness->kind()) {
       case Value::Kind::ImplWitness: {
         auto* impl_witness = cast<ImplWitness>(witness);
@@ -156,10 +169,10 @@ static auto SetFieldImpl(
   switch (value->kind()) {
     case Value::Kind::StructValue: {
       std::vector<NamedValue> elements = cast<StructValue>(*value).elements();
-      auto it = std::find_if(elements.begin(), elements.end(),
-                             [path_begin](const NamedValue& element) {
-                               return element.name == (*path_begin).name();
-                             });
+      auto it =
+          llvm::find_if(elements, [path_begin](const NamedValue& element) {
+            return element.name == (*path_begin).name();
+          });
       if (it == elements.end()) {
         return RuntimeError(source_loc)
                << "field " << (*path_begin).name() << " not in " << *value;
@@ -397,7 +410,7 @@ void Value::Print(llvm::raw_ostream& out) const {
         out << combine << *ctx.context;
       }
       out << " where ";
-      llvm::ListSeparator sep;
+      llvm::ListSeparator sep(" and ");
       for (const ConstraintType::ImplConstraint& impl :
            constraint.impl_constraints()) {
         // TODO: Skip cases where `impl.type` is `.Self` and the interface is
@@ -453,6 +466,11 @@ void Value::Print(llvm::raw_ostream& out) const {
     case Value::Kind::VariableType:
       out << cast<VariableType>(*this).binding();
       break;
+    case Value::Kind::AssociatedConstant: {
+      const auto& assoc = cast<AssociatedConstant>(*this);
+      out << "(" << assoc.base() << ")." << assoc.constant().binding().name();
+      break;
+    }
     case Value::Kind::ContinuationValue: {
       out << cast<ContinuationValue>(*this).stack();
       break;
@@ -540,30 +558,36 @@ void ContinuationValue::StackFragment::Print(llvm::raw_ostream& out) const {
 
 // 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 {
+static auto BindingMapEqual(
+    const BindingMap& map1, const BindingMap& map2,
+    std::optional<Nonnull<const EqualityContext*>> equality_ctx) -> bool {
   CARBON_CHECK(map1.size() == map2.size()) << "maps should have same keys";
   for (const auto& [key, value] : map1) {
-    if (!ValueEqual(value, map2.at(key))) {
+    if (!ValueEqual(value, map2.at(key), equality_ctx)) {
       return false;
     }
   }
   return true;
 }
 
-auto TypeEqual(Nonnull<const Value*> t1, Nonnull<const Value*> t2) -> bool {
+auto TypeEqual(Nonnull<const Value*> t1, Nonnull<const Value*> t2,
+               std::optional<Nonnull<const EqualityContext*>> equality_ctx)
+    -> bool {
   if (t1->kind() != t2->kind()) {
+    if (isa<AssociatedConstant>(t1) || isa<AssociatedConstant>(t2)) {
+      return ValueEqual(t1, t2, equality_ctx);
+    }
     return false;
   }
   switch (t1->kind()) {
     case Value::Kind::PointerType:
       return TypeEqual(&cast<PointerType>(*t1).type(),
-                       &cast<PointerType>(*t2).type());
+                       &cast<PointerType>(*t2).type(), equality_ctx);
     case Value::Kind::FunctionType: {
       const auto& fn1 = cast<FunctionType>(*t1);
       const auto& fn2 = cast<FunctionType>(*t2);
-      return TypeEqual(&fn1.parameters(), &fn2.parameters()) &&
-             TypeEqual(&fn1.return_type(), &fn2.return_type());
+      return TypeEqual(&fn1.parameters(), &fn2.parameters(), equality_ctx) &&
+             TypeEqual(&fn1.return_type(), &fn2.return_type(), equality_ctx);
     }
     case Value::Kind::StructType: {
       const auto& struct1 = cast<StructType>(*t1);
@@ -573,7 +597,8 @@ auto TypeEqual(Nonnull<const Value*> t1, Nonnull<const Value*> t2) -> bool {
       }
       for (size_t i = 0; i < struct1.fields().size(); ++i) {
         if (struct1.fields()[i].name != struct2.fields()[i].name ||
-            !TypeEqual(struct1.fields()[i].value, struct2.fields()[i].value)) {
+            !TypeEqual(struct1.fields()[i].value, struct2.fields()[i].value,
+                       equality_ctx)) {
           return false;
         }
       }
@@ -583,14 +608,18 @@ auto TypeEqual(Nonnull<const Value*> t1, Nonnull<const Value*> t2) -> bool {
       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());
+             BindingMapEqual(class1.type_args(), class2.type_args(),
+                             equality_ctx);
     }
     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());
+             BindingMapEqual(iface1.args(), iface2.args(), equality_ctx);
     }
+    case Value::Kind::AssociatedConstant:
+      // Associated constants are sometimes types.
+      return ValueEqual(t1, t2, equality_ctx);
     case Value::Kind::ConstraintType: {
       const auto& constraint1 = cast<ConstraintType>(*t1);
       const auto& constraint2 = cast<ConstraintType>(*t2);
@@ -605,8 +634,8 @@ auto TypeEqual(Nonnull<const Value*> t1, Nonnull<const Value*> t2) -> bool {
       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)) {
+        if (!TypeEqual(impl1.type, impl2.type, equality_ctx) ||
+            !TypeEqual(impl1.interface, impl2.interface, equality_ctx)) {
           return false;
         }
       }
@@ -617,7 +646,8 @@ auto TypeEqual(Nonnull<const Value*> t1, Nonnull<const Value*> t2) -> bool {
           return false;
         }
         for (size_t j = 0; j < equality1.values.size(); ++j) {
-          if (!ValueEqual(equality1.values[i], equality2.values[i])) {
+          if (!ValueEqual(equality1.values[i], equality2.values[i],
+                          equality_ctx)) {
             return false;
           }
         }
@@ -625,7 +655,7 @@ auto TypeEqual(Nonnull<const Value*> t1, Nonnull<const Value*> t2) -> bool {
       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)) {
+        if (!TypeEqual(context1.context, context2.context, equality_ctx)) {
           return false;
         }
       }
@@ -640,7 +670,7 @@ auto TypeEqual(Nonnull<const Value*> t1, Nonnull<const Value*> t2) -> bool {
         return false;
       }
       for (size_t i = 0; i < tup1.elements().size(); ++i) {
-        if (!TypeEqual(tup1.elements()[i], tup2.elements()[i])) {
+        if (!TypeEqual(tup1.elements()[i], tup2.elements()[i], equality_ctx)) {
           return false;
         }
       }
@@ -657,20 +687,24 @@ auto TypeEqual(Nonnull<const Value*> t1, Nonnull<const Value*> t2) -> bool {
              &cast<VariableType>(*t2).binding();
     case Value::Kind::TypeOfClassType:
       return TypeEqual(&cast<TypeOfClassType>(*t1).class_type(),
-                       &cast<TypeOfClassType>(*t2).class_type());
+                       &cast<TypeOfClassType>(*t2).class_type(), equality_ctx);
     case Value::Kind::TypeOfInterfaceType:
       return TypeEqual(&cast<TypeOfInterfaceType>(*t1).interface_type(),
-                       &cast<TypeOfInterfaceType>(*t2).interface_type());
+                       &cast<TypeOfInterfaceType>(*t2).interface_type(),
+                       equality_ctx);
     case Value::Kind::TypeOfConstraintType:
       return TypeEqual(&cast<TypeOfConstraintType>(*t1).constraint_type(),
-                       &cast<TypeOfConstraintType>(*t2).constraint_type());
+                       &cast<TypeOfConstraintType>(*t2).constraint_type(),
+                       equality_ctx);
     case Value::Kind::TypeOfChoiceType:
       return TypeEqual(&cast<TypeOfChoiceType>(*t1).choice_type(),
-                       &cast<TypeOfChoiceType>(*t2).choice_type());
+                       &cast<TypeOfChoiceType>(*t2).choice_type(),
+                       equality_ctx);
     case Value::Kind::StaticArrayType: {
       const auto& array1 = cast<StaticArrayType>(*t1);
       const auto& array2 = cast<StaticArrayType>(*t2);
-      return TypeEqual(&array1.element_type(), &array2.element_type()) &&
+      return TypeEqual(&array1.element_type(), &array2.element_type(),
+                       equality_ctx) &&
              array1.size() == array2.size();
     }
     case Value::Kind::IntValue:
@@ -704,10 +738,11 @@ auto TypeEqual(Nonnull<const Value*> t1, Nonnull<const Value*> t2) -> bool {
   }
 }
 
-// Returns true if the two values are equal and returns false otherwise.
-//
-// This function implements the `==` operator of Carbon.
-auto ValueEqual(Nonnull<const Value*> v1, Nonnull<const Value*> v2) -> bool {
+// Returns true if the two values are known to be equal and are written in the
+// same way at the top level.
+auto ValueStructurallyEqual(
+    Nonnull<const Value*> v1, Nonnull<const Value*> v2,
+    std::optional<Nonnull<const EqualityContext*>> equality_ctx) -> bool {
   if (v1->kind() != v2->kind()) {
     return false;
   }
@@ -729,7 +764,7 @@ auto ValueEqual(Nonnull<const Value*> v1, Nonnull<const Value*> v2) -> bool {
       const auto& m2 = cast<BoundMethodValue>(*v2);
       std::optional<Nonnull<const Statement*>> body1 = m1.declaration().body();
       std::optional<Nonnull<const Statement*>> body2 = m2.declaration().body();
-      return ValueEqual(m1.receiver(), m2.receiver()) &&
+      return ValueEqual(m1.receiver(), m2.receiver(), equality_ctx) &&
              body1.has_value() == body2.has_value() &&
              (!body1.has_value() || *body1 == *body2);
     }
@@ -742,7 +777,7 @@ auto ValueEqual(Nonnull<const Value*> v1, Nonnull<const Value*> v2) -> bool {
         return false;
       }
       for (size_t i = 0; i < elements1.size(); ++i) {
-        if (!ValueEqual(elements1[i], elements2[i])) {
+        if (!ValueEqual(elements1[i], elements2[i], equality_ctx)) {
           return false;
         }
       }
@@ -756,7 +791,7 @@ auto ValueEqual(Nonnull<const Value*> v1, Nonnull<const Value*> v2) -> bool {
         CARBON_CHECK(struct_v1.elements()[i].name ==
                      struct_v2.elements()[i].name);
         if (!ValueEqual(struct_v1.elements()[i].value,
-                        struct_v2.elements()[i].value)) {
+                        struct_v2.elements()[i].value, equality_ctx)) {
           return false;
         }
       }
@@ -773,6 +808,14 @@ auto ValueEqual(Nonnull<const Value*> v1, Nonnull<const Value*> v2) -> bool {
           << "parameterized name refers to unnamed declaration";
       return *name1 == *name2;
     }
+    case Value::Kind::AssociatedConstant: {
+      // The witness value is not part of determining value equality.
+      const auto& assoc1 = cast<AssociatedConstant>(*v1);
+      const auto& assoc2 = cast<AssociatedConstant>(*v2);
+      return &assoc1.constant() == &assoc2.constant() &&
+             TypeEqual(&assoc1.base(), &assoc2.base(), equality_ctx) &&
+             TypeEqual(&assoc1.interface(), &assoc2.interface(), equality_ctx);
+    }
     case Value::Kind::IntType:
     case Value::Kind::BoolType:
     case Value::Kind::TypeType:
@@ -796,7 +839,7 @@ auto ValueEqual(Nonnull<const Value*> v1, Nonnull<const Value*> v2) -> bool {
     case Value::Kind::TypeOfParameterizedEntityName:
     case Value::Kind::TypeOfMemberName:
     case Value::Kind::StaticArrayType:
-      return TypeEqual(v1, v2);
+      return TypeEqual(v1, v2, equality_ctx);
     case Value::Kind::NominalClassValue:
     case Value::Kind::AlternativeValue:
     case Value::Kind::BindingPlaceholderValue:
@@ -813,6 +856,78 @@ auto ValueEqual(Nonnull<const Value*> v1, Nonnull<const Value*> v2) -> bool {
   }
 }
 
+// Returns true if the two values are equal and returns false otherwise.
+//
+// This function implements the `==` operator of Carbon.
+auto ValueEqual(Nonnull<const Value*> v1, Nonnull<const Value*> v2,
+                std::optional<Nonnull<const EqualityContext*>> equality_ctx)
+    -> bool {
+  // If we're given an equality context, check to see if it knows these values
+  // are equal. Only perform the check if one or the other value is an
+  // associated constant; otherwise we should be able to do better by looking
+  // at the structures of the values.
+  if (equality_ctx) {
+    if (isa<AssociatedConstant>(v1)) {
+      auto visitor = [&](Nonnull<const Value*> maybe_v2) {
+        return !ValueStructurallyEqual(v2, maybe_v2, equality_ctx);
+      };
+      if (!(*equality_ctx)->VisitEqualValues(v1, visitor)) {
+        return true;
+      }
+    }
+    if (isa<AssociatedConstant>(v2)) {
+      auto visitor = [&](Nonnull<const Value*> maybe_v1) {
+        return !ValueStructurallyEqual(v1, maybe_v1, equality_ctx);
+      };
+      if (!(*equality_ctx)->VisitEqualValues(v2, visitor)) {
+        return true;
+      }
+    }
+  }
+
+  return ValueStructurallyEqual(v1, v2, equality_ctx);
+}
+
+auto EqualityConstraint::VisitEqualValues(
+    Nonnull<const Value*> value,
+    llvm::function_ref<bool(Nonnull<const Value*>)> visitor) const -> bool {
+  // See if the given value is part of this constraint.
+  auto first_equal = llvm::find_if(values, [value](Nonnull<const Value*> val) {
+    return ValueEqual(value, val, std::nullopt);
+  });
+  if (first_equal == values.end()) {
+    return true;
+  }
+
+  // The value is in this group; pass all non-identical values in the group
+  // to the visitor. First visit the values we already compared.
+  for (auto* val : llvm::make_range(values.begin(), first_equal)) {
+    if (!visitor(val)) {
+      return false;
+    }
+  }
+  // Then visit any remaining non-identical values, skipping the one we already
+  // found was identical.
+  ++first_equal;
+  for (auto* val : llvm::make_range(first_equal, values.end())) {
+    if (!ValueEqual(value, val, std::nullopt) && !visitor(val)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+auto ConstraintType::VisitEqualValues(
+    Nonnull<const Value*> value,
+    llvm::function_ref<bool(Nonnull<const Value*>)> visitor) const -> bool {
+  for (const auto& eq : equality_constraints()) {
+    if (!eq.VisitEqualValues(value, visitor)) {
+      return false;
+    }
+  }
+  return true;
+}
+
 auto ChoiceType::FindAlternative(std::string_view name) const
     -> std::optional<Nonnull<const Value*>> {
   for (const NamedValue& alternative : alternatives_) {

+ 92 - 7
explorer/interpreter/value.h

@@ -24,6 +24,7 @@
 namespace Carbon {
 
 class Action;
+class ImplScope;
 
 // Abstract base class of all AST nodes representing values.
 //
@@ -61,6 +62,7 @@ class Value {
     ChoiceType,
     ContinuationType,  // The type of a continuation.
     VariableType,      // e.g., generic type parameters.
+    AssociatedConstant,
     ParameterizedEntityName,
     MemberName,
     BindingPlaceholderValue,
@@ -113,6 +115,26 @@ class Value {
   const Kind kind_;
 };
 
+// Base class for types holding contextual information by which we can
+// determine whether values are equal.
+class EqualityContext {
+ public:
+  virtual auto VisitEqualValues(
+      Nonnull<const Value*> value,
+      llvm::function_ref<bool(Nonnull<const Value*>)> visitor) const
+      -> bool = 0;
+
+ protected:
+  ~EqualityContext() = default;
+};
+
+auto TypeEqual(Nonnull<const Value*> t1, Nonnull<const Value*> t2,
+               std::optional<Nonnull<const EqualityContext*>> equality_ctx)
+    -> bool;
+auto ValueEqual(Nonnull<const Value*> v1, Nonnull<const Value*> v2,
+                std::optional<Nonnull<const EqualityContext*>> equality_ctx)
+    -> bool;
+
 // An integer value.
 class IntValue : public Value {
  public:
@@ -631,6 +653,23 @@ class InterfaceType : public Value {
   Nonnull<const Bindings*> bindings_ = Bindings::None();
 };
 
+// A collection of values that are known to be the same.
+struct EqualityConstraint {
+  // Visit the values in this equality constraint that are a single step away
+  // from the given value according to this equality constraint. That is: if
+  // `value` is identical to a value in `values`, then call the visitor on all
+  // values in `values` that are not identical to `value`. Otherwise, do not
+  // call the visitor.
+  //
+  // Stops and returns `false` if any call to the visitor returns `false`,
+  // otherwise returns `true`.
+  auto VisitEqualValues(
+      Nonnull<const Value*> value,
+      llvm::function_ref<bool(Nonnull<const Value*>)> visitor) const -> bool;
+
+  std::vector<Nonnull<const Value*>> values;
+};
+
 // A type-of-type for an unknown constrained type.
 //
 // These types are formed by the `&` operator that combines constraints and by
@@ -655,10 +694,7 @@ class ConstraintType : public Value {
     Nonnull<const InterfaceType*> interface;
   };
 
-  // A collection of values that are known to be the same.
-  struct EqualityConstraint {
-    std::vector<Nonnull<const Value*>> values;
-  };
+  using EqualityConstraint = Carbon::EqualityConstraint;
 
   // A context in which we might look up a name.
   struct LookupContext {
@@ -696,6 +732,17 @@ class ConstraintType : public Value {
     return lookup_contexts_;
   }
 
+  // Visit the values in that are a single step away from the given value
+  // according to equality constraints in this constraint type, that is, the
+  // values `v` that are not identical to `value` but for which we have a
+  // `value == v` equality constraint in this constraint type.
+  //
+  // Stops and returns `false` if any call to the visitor returns `false`,
+  // otherwise returns `true`.
+  auto VisitEqualValues(
+      Nonnull<const Value*> value,
+      llvm::function_ref<bool(Nonnull<const Value*>)> visitor) const -> bool;
+
  private:
   Nonnull<const GenericBinding*> self_binding_;
   std::vector<ImplConstraint> impl_constraints_;
@@ -882,6 +929,47 @@ class MemberName : public Value {
   Member member_;
 };
 
+// A symbolic value representing an associated constant.
+//
+// This is a value of the form `A.B` or `A.B.C` or similar, where `A` is a
+// `VariableType`.
+class AssociatedConstant : public Value {
+ public:
+  explicit AssociatedConstant(
+      Nonnull<const Value*> base, Nonnull<const InterfaceType*> interface,
+      Nonnull<const AssociatedConstantDeclaration*> constant,
+      Nonnull<const Witness*> witness)
+      : Value(Kind::AssociatedConstant),
+        base_(base),
+        interface_(interface),
+        constant_(constant),
+        witness_(witness) {}
+
+  static auto classof(const Value* value) -> bool {
+    return value->kind() == Kind::AssociatedConstant;
+  }
+
+  // The type for which we denote an associated constant.
+  auto base() const -> const Value& { return *base_; }
+
+  // The interface within which the constant was declared.
+  auto interface() const -> const InterfaceType& { return *interface_; }
+
+  // The associated constant whose value is being denoted.
+  auto constant() const -> const AssociatedConstantDeclaration& {
+    return *constant_;
+  }
+
+  // Witness within which the constant's value can be found.
+  auto witness() const -> const Witness& { return *witness_; }
+
+ private:
+  Nonnull<const Value*> base_;
+  Nonnull<const InterfaceType*> interface_;
+  Nonnull<const AssociatedConstantDeclaration*> constant_;
+  Nonnull<const Witness*> witness_;
+};
+
 // A first-class continuation representation of a fragment of the stack.
 // A continuation value behaves like a pointer to the underlying stack
 // fragment, which is exposed by `Stack()`.
@@ -1100,9 +1188,6 @@ class StaticArrayType : public Value {
   size_t size_;
 };
 
-auto TypeEqual(Nonnull<const Value*> t1, Nonnull<const Value*> t2) -> bool;
-auto ValueEqual(Nonnull<const Value*> v1, Nonnull<const Value*> v2) -> bool;
-
 }  // namespace Carbon
 
 #endif  // CARBON_EXPLORER_INTERPRETER_VALUE_H_

+ 6 - 0
explorer/syntax/parser.ypp

@@ -1034,6 +1034,12 @@ interface_body:
       $$ = $1;
       $$.push_back($2);
     }
+| interface_body LET generic_binding SEMICOLON
+    {
+      $$ = $1;
+      $$.push_back(
+          arena->New<AssociatedConstantDeclaration>(context.source_loc(), $3));
+    }
 ;
 impl_body:
   // Empty

+ 18 - 0
explorer/testdata/assoc_const/fail_anonymous.carbon

@@ -0,0 +1,18 @@
+// 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 Iface {
+  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/assoc_const/fail_anonymous.carbon:[[@LINE+1]]: syntax error, unexpected UNDERSCORE, expecting identifier
+  let _:! Type;
+}
+
+fn Main() -> i32 { return 0; }

+ 22 - 0
explorer/testdata/assoc_const/fail_incomplete_impl_1.carbon

@@ -0,0 +1,22 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// 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 HasThreeTypes {
+  let A:! Type;
+  let B:! Type;
+  let C:! Type;
+}
+
+// CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/assoc_const/fail_incomplete_impl_1.carbon:[[@LINE+1]]: implementation missing (.Self:! HasThreeTypes).B
+external impl i32 as HasThreeTypes where .A == i32 and .C == i32 {}
+
+fn Main() -> i32 { return 0; }

+ 22 - 0
explorer/testdata/assoc_const/fail_incomplete_impl_2.carbon

@@ -0,0 +1,22 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// 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 HasThreeTypes {
+  let A:! Type;
+  let B:! Type;
+  let C:! Type;
+}
+
+// CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/assoc_const/fail_incomplete_impl_2.carbon:[[@LINE+1]]: implementation doesn't provide a concrete value for (.Self:! HasThreeTypes).B
+external impl i32 as HasThreeTypes where .A == i32 and .B == .C {}
+
+fn Main() -> i32 { return 0; }

+ 36 - 0
explorer/testdata/assoc_const/fail_indirectly_equal.carbon

@@ -0,0 +1,36 @@
+// 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 {
+  let T:! Type;
+}
+
+fn F1[T:! A where .T == i32](x: T.T) -> i32 {
+  // OK: one equality.
+  return x;
+}
+
+fn F2[U:! A where .T == i32](x: i32) -> U.T {
+  // OK: one equality.
+  return x;
+}
+
+fn F3[T:! A where .T == i32, U:! A where .T == i32](x: T.T) -> U.T {
+  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/assoc_const/fail_indirectly_equal.carbon:[[@LINE+1]]: type error in return value: '(T:! A where .Self.T == i32).T' is not implicitly convertible to '(U:! A where .Self.T == i32).T'
+  return x;
+}
+
+external impl i32 as A where .T == i32 {}
+
+fn Main() -> i32 {
+  return F3(0);
+}

+ 38 - 0
explorer/testdata/assoc_const/fail_match_in_deduction.carbon

@@ -0,0 +1,38 @@
+// 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
+
+// TODO: Should this work?
+
+package ExplorerTest api;
+
+interface Vector {
+  let Dim:! i32;
+}
+external impl (i32, i32, i32) as Vector where .Dim == 3 {}
+
+class Point(Scalar:! Type, Dim:! i32) {}
+
+fn F[Scalar:! Type, V:! Vector where .Dim == 3](p: Point(Scalar, V.Dim), v: V) {}
+
+fn G[Scalar:! Type](p: Point(Scalar, 3)) {}
+fn H[V:! Vector where .Dim == 3](v: V) {
+  var p: Point(i32, V.Dim) = {};
+  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/assoc_const/fail_match_in_deduction.carbon:[[@LINE+1]]: mismatch in non-type values, `(V:! Vector where .Self.Dim == 3).Dim` != `3`
+  G(p);
+}
+
+fn Main() -> i32 {
+  var p: Point(i32, 3) = {};
+  // Deduce Point(Scalar, V.Dim) from Point(i32, 3).
+  F(p, (0, 0, 0));
+  // Deduce Point(Scalar, 3) from Point(i32, V.Dim).
+  H((0, 0, 0));
+  return 0;
+}

+ 46 - 0
explorer/testdata/assoc_const/fail_multi_impl_scoping.carbon

@@ -0,0 +1,46 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// 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 {
+  let TA:! Type;
+  fn FA() -> TA;
+}
+interface B {
+  let TB:! Type;
+  fn FB() -> TB;
+}
+
+class C(T:! Type) {
+  impl as A & B where .TA == i32 and .TB == i32 {
+    fn FA() -> i32 {
+      // OK, know that TA is i32 here.
+      let v: Self.(A.TA) = 1;
+      let w: i32 = v;
+      return w;
+    }
+    fn FB() -> i32 {
+      // OK, know that TB is i32 here.
+      let v: Self.(B.TB) = 2;
+      // TODO: Don't know that TA is i32. It could be specialized.
+      // We should reject this once we support specialization.
+      let w: Self.(A.TA) = 3;
+      return v + w;
+    }
+  }
+}
+
+external impl C(i32) as B where .TB == () {
+  fn FB() -> () { return (); }
+// CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/assoc_const/fail_multi_impl_scoping.carbon:[[@LINE+1]]: ambiguous implementations of interface B for class C(T = i32)
+}
+
+fn Main() -> i32 { return C(i32).FB(); }

+ 27 - 0
explorer/testdata/assoc_const/fail_multiple_deduction.carbon

@@ -0,0 +1,27 @@
+// 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 HasThreeTypes {
+  let A:! Type;
+  let B:! Type;
+  let C:! Type;
+  fn Make[me: Self]() -> (A, B, C);
+}
+fn F[T:! Type](x: (T, T, T));
+fn G[X:! HasThreeTypes where .A == .B and .B == .C and .C == .A](x: X) {
+  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/assoc_const/fail_multiple_deduction.carbon:[[@LINE+3]]: deduced multiple different values for T:! Type:
+  // CHECK:   (X:! HasThreeTypes where .Self.A == .Self.B and .Self.B == .Self.C and .Self.C == .Self.A).A
+  // CHECK:   (X:! HasThreeTypes where .Self.A == .Self.B and .Self.B == .Self.C and .Self.C == .Self.A).B
+  F(x.Make());
+}
+
+fn Main() -> i32 { return 0; }

+ 20 - 0
explorer/testdata/assoc_const/fail_overspecified_impl.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} %{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 HasType {
+  let T:! Type;
+}
+
+// CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/assoc_const/fail_overspecified_impl.carbon:[[@LINE+1]]: implementation provides multiple values for (.Self:! HasType).T: i32 and {.a: i32}
+external impl i32 as HasType where .T == i32 and .T == {.a: i32} {}
+
+fn Main() -> i32 { return 0; }

+ 19 - 0
explorer/testdata/assoc_const/fail_redefined.carbon

@@ -0,0 +1,19 @@
+// 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 Iface {
+  let T:! Type;
+  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/assoc_const/fail_redefined.carbon:[[@LINE+1]]: Duplicate name `T` also found at {{.*}}/explorer/testdata/assoc_const/fail_redefined.carbon:14
+  let T:! Type;
+}
+
+fn Main() -> i32 { return 0; }

+ 27 - 0
explorer/testdata/assoc_const/fail_unknown_value.carbon

@@ -0,0 +1,27 @@
+// 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 Iface { let N:! i32; }
+
+fn PickType(N: i32) -> Type { return i32; }
+
+fn F[T:! Iface](x: T) -> i32 {
+  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/assoc_const/fail_unknown_value.carbon:[[@LINE+1]]: value of associated constant (T:! Iface).N is not known
+  var x: PickType(T.N) = 0;
+  return x;
+}
+
+impl i32 as Iface where .N == 5 {}
+
+fn Main() -> i32 {
+  return F(0);
+}

+ 28 - 0
explorer/testdata/assoc_const/fail_unknown_value_specified_in_constraint.carbon

@@ -0,0 +1,28 @@
+// 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 Iface { let N:! i32; }
+
+fn PickType(N: i32) -> Type { return i32; }
+
+fn F[T:! Iface where .N == 5](x: T) -> i32 {
+  // TODO: This should be valid: the value of T.N is known to be 5 here.
+  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/assoc_const/fail_unknown_value_specified_in_constraint.carbon:[[@LINE+1]]: value of associated constant (T:! Iface where .Self.N == 5).N is not known
+  var x: PickType(T.N) = 0;
+  return x;
+}
+
+impl i32 as Iface where .N == 5 {}
+
+fn Main() -> i32 {
+  return F(0);
+}

+ 40 - 0
explorer/testdata/assoc_const/impl_lookup.carbon

@@ -0,0 +1,40 @@
+// 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: 1
+
+package ExplorerTest api;
+
+interface Frob {
+  let Result:! Type;
+  fn F[me: Self]() -> Result;
+}
+
+fn Use[T:! Frob](x: T) -> T.Result {
+  var v: T.Result = x.F();
+  return v;
+}
+
+class AlmostI32 {
+  var val: i32;
+  impl as ImplicitAs(i32) {
+    fn Convert[me: Self]() -> i32 { return me.val; }
+  }
+}
+
+impl i32 as Frob where .Result == AlmostI32 {
+  fn F[me: Self]() -> AlmostI32 { return {.val = me}; }
+}
+
+fn Main() -> i32 {
+  // Ensure that lookup for
+  //   i32.(Frob.Result) as ImplicitAs(i32)
+  // finds `impl AlmostI32 as ImplicitAs(i32)`.
+  return Use(1);
+}

+ 27 - 0
explorer/testdata/assoc_const/implement.carbon

@@ -0,0 +1,27 @@
+// 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: 2
+
+package ExplorerTest api;
+
+interface Vector {
+  let Dim:! i32;
+}
+
+class Point {
+  var x: i32;
+  var y: i32;
+  impl as Vector where .Dim == 2 {}
+}
+
+fn Main() -> i32 {
+  var a: Point = {.x = 2, .y = 1};
+  return a.(Vector.Dim);
+}

+ 30 - 0
explorer/testdata/assoc_const/simple_constraint.carbon

@@ -0,0 +1,30 @@
+// 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: 3
+
+package ExplorerTest api;
+
+interface Frob {
+  let Result:! Type;
+  fn F[me: Self]() -> Result;
+}
+
+fn Use[T:! Frob where .Result == .Self](x: T) -> T {
+  var v: T = x.F();
+  return v;
+}
+
+impl i32 as Frob where .Result == i32 {
+  fn F[me: Self]() -> i32 { return me + 1; }
+}
+
+fn Main() -> i32 {
+  return Use(2);
+}

+ 30 - 0
explorer/testdata/assoc_const/simple_equality.carbon

@@ -0,0 +1,30 @@
+// 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: 0
+
+package ExplorerTest api;
+
+interface Frob {
+  let Result:! Type;
+  fn F[me: Self]() -> Result;
+}
+
+fn Use[T:! Frob](x: T) -> T.Result {
+  var v: T.Result = x.F();
+  return v;
+}
+
+impl i32 as Frob where .Result == i32 {
+  fn F[me: Self]() -> i32 { return 0; }
+}
+
+fn Main() -> i32 {
+  return Use(0);
+}

+ 3 - 3
explorer/testdata/constraint/fail_where_equals_different_types.carbon

@@ -12,9 +12,9 @@ package ExplorerTest api;
 
 interface A {}
 
-// CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/constraint/fail_where_equals_different_types.carbon:[[@LINE+3]]: type error in values in `where ==` constraint
-// CHECK: expected: i32
-// CHECK: actual: Type
+// CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/constraint/fail_where_equals_different_types.carbon:[[@LINE+3]]: type mismatch between values in `where LHS == RHS`
+// CHECK:   LHS type: i32
+// CHECK:   RHS type: Type
 alias B = A where 4 == i32;
 
 fn Main() -> i32 { return 0; }

+ 1 - 1
explorer/testdata/constraint/missing_member.carbon

@@ -13,7 +13,7 @@ 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
+// 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 and .Self:! Type is interface B
 fn Get[T:! A & B](n: T) -> i32 { return n.H(); }
 
 impl i32 as A {

+ 1 - 1
explorer/testdata/constraint/combine_equality.carbon → explorer/testdata/constraint/no_combine_equality.carbon

@@ -15,7 +15,7 @@ impl i32 as I {}
 
 fn F(A:! i32, B:! i32, C:! i32, D:! i32, E:! i32,
      T:! I where A == B and C == D and C == E and B == D) {
-  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/constraint/combine_equality.carbon:[[@LINE+1]]: member access, F not in constraint interface I where .Self:! I is interface I, A:! i32 == B:! i32 == E:! i32 == C:! i32 == D:! i32
+  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/constraint/no_combine_equality.carbon:[[@LINE+1]]: member access, F not in constraint interface I where .Self:! I is interface I and A:! i32 == B:! i32 and C:! i32 == D:! i32 and C:! i32 == E:! i32 and B:! i32 == D:! i32
   T.F();
 }
 

+ 3 - 3
explorer/testdata/generic_class/fail_argument_deduction.carbon

@@ -22,8 +22,8 @@ fn FirstOfTwoPoints[T:! Type](a: Point(T), b: Point(T)) -> Point(T) {
 fn Main() -> i32 {
   var p: Point(i32) = {.x = 0, .y = 1};
   var q: Point(Bool) = {.x = true, .y = false};
-  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/generic_class/fail_argument_deduction.carbon:[[@LINE+3]]: type error in repeated argument deduction
-  // CHECK: expected: i32
-  // CHECK: actual: Bool
+  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/generic_class/fail_argument_deduction.carbon:[[@LINE+3]]: deduced multiple different values for T:! Type:
+  // CHECK:   i32
+  // CHECK:   Bool
   return FirstOfTwoPoints(p, q).x;
 }

+ 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 into unconstrained type
+  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/generic_class/fail_field_access_on_generic.carbon:[[@LINE+1]]: member access in unconstrained type
   return a.x;
 }
 

+ 3 - 3
explorer/testdata/generic_function/fail_type_deduction_mismatch.carbon

@@ -15,8 +15,8 @@ fn fst[T:! Type](x: T, y: T) -> T {
 }
 
 fn Main() -> i32 {
-  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/generic_function/fail_type_deduction_mismatch.carbon:[[@LINE+3]]: type error in repeated argument deduction
-  // CHECK: expected: i32
-  // CHECK: actual: Bool
+  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/generic_function/fail_type_deduction_mismatch.carbon:[[@LINE+3]]: deduced multiple different values for T:! Type:
+  // CHECK:   i32
+  // CHECK:   Bool
   return fst(0, true);
 }

+ 5 - 5
explorer/testdata/impl/impl_constraint.carbon

@@ -7,7 +7,7 @@
 // RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
 // RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
 // AUTOUPDATE: %{explorer} %s
-// CHECK: result: 1232
+// CHECK: result: 1234
 
 package ExplorerTest api;
 
@@ -19,20 +19,20 @@ interface B {
   fn G() -> i32;
 }
 interface C(T:! Type) {
-  fn F() -> T;
+  fn H() -> T;
 }
 
 external impl i32 as A {
   fn F() -> i32 { return 1; }
 }
 
-// TODO: Use `where .Self is A` once `.Self` support lands.
-external impl i32 as B & C(i32) where i32 is A {
+external impl i32 as B & C(i32) where .Self is A {
   fn F() -> i32 { return 2; }
   fn G() -> i32 { return 3; }
+  fn H() -> i32 { return 4; }
 }
 
 fn Main() -> i32 {
   let n: i32 = 0;
-  return n.(A.F)() * 1000 + n.(B.F)() * 100 + n.(B.G)() * 10 + n.(C(i32).F)();
+  return n.(A.F)() * 1000 + n.(B.F)() * 100 + n.(B.G)() * 10 + n.(C(i32).H)();
 }

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

@@ -11,7 +11,7 @@
 package ExplorerTest api;
 
 interface Bad {
-  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/interface/fail_bad_member_kind.carbon:[[@LINE+1]]: syntax error, unexpected VAR, expecting FN or RIGHT_CURLY_BRACE
+  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/interface/fail_bad_member_kind.carbon:[[@LINE+1]]: syntax error, unexpected VAR, expecting FN or LET or RIGHT_CURLY_BRACE
   var V: i32;
 }
 

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio