Quellcode durchsuchen

Explorer: Mixin phase 1 (#2069)

* Replay changes from pre-force-push mixin branch

* Base MixinPseudoType off of the new InterfaceType

* Add more test cases.

Also removed an unnecessary check that would have already been
handled by the parser.

* WIP detect member clashes during mixing mixins

* Implement fuzzer changes

* Implement member name clash check when mixing mixins

* Modify parser and lexer for experimental mixin feature

* Add comments

* Update explorer/testdata/mixin/simple-mix-in-mixin.carbon

Co-authored-by: josh11b <josh11b@users.noreply.github.com>

* Update explorer/testdata/mixin/use-mixin-method-in-class-method.carbon

Co-authored-by: josh11b <josh11b@users.noreply.github.com>

* Make code review changes

Co-authored-by: josh11b <josh11b@users.noreply.github.com>
Darshal Shetty vor 3 Jahren
Ursprung
Commit
037c99e2fb
30 geänderte Dateien mit 1219 neuen und 28 gelöschten Zeilen
  1. 16 0
      common/fuzzing/carbon.proto
  2. 30 0
      common/fuzzing/proto_to_carbon.cpp
  3. 2 0
      explorer/ast/ast_rtti.txt
  4. 34 2
      explorer/ast/declaration.cpp
  5. 65 0
      explorer/ast/declaration.h
  6. 23 0
      explorer/fuzzing/ast_to_proto.cpp
  7. 4 0
      explorer/interpreter/interpreter.cpp
  8. 8 0
      explorer/interpreter/resolve_control_flow.cpp
  9. 31 0
      explorer/interpreter/resolve_names.cpp
  10. 2 0
      explorer/interpreter/resolve_unformed.cpp
  11. 278 23
      explorer/interpreter/type_checker.cpp
  12. 54 2
      explorer/interpreter/type_checker.h
  13. 63 0
      explorer/interpreter/value.cpp
  14. 52 0
      explorer/interpreter/value.h
  15. 4 0
      explorer/syntax/lexer.lpp
  16. 58 1
      explorer/syntax/parser.ypp
  17. 38 0
      explorer/testdata/mixin/fail-circular-mixing.carbon
  18. 27 0
      explorer/testdata/mixin/fail-field-member-name-clash.carbon
  19. 29 0
      explorer/testdata/mixin/fail-method-member-name-clash.carbon
  20. 19 0
      explorer/testdata/mixin/fail-mix-as-type-expr.carbon
  21. 38 0
      explorer/testdata/mixin/fail-mix-diamond-clash.carbon
  22. 49 0
      explorer/testdata/mixin/fail-mix-in-global.carbon
  23. 29 0
      explorer/testdata/mixin/fail-mix-in-impl.carbon
  24. 31 0
      explorer/testdata/mixin/fail-mix-members-clash.carbon
  25. 26 0
      explorer/testdata/mixin/fail-recursive-mixing.carbon
  26. 45 0
      explorer/testdata/mixin/fail-self-substitution.carbon
  27. 39 0
      explorer/testdata/mixin/self-substitution.carbon
  28. 34 0
      explorer/testdata/mixin/simple-mix-in-mixin.carbon
  29. 47 0
      explorer/testdata/mixin/simple-reuse.carbon
  30. 44 0
      explorer/testdata/mixin/use-mixin-method-in-class-method.carbon

+ 16 - 0
common/fuzzing/carbon.proto

@@ -389,6 +389,20 @@ message AliasDeclaration {
   optional Expression target = 2;
 }
 
+// EXPERIMENTAL MIXIN FEATURE
+message MixinDeclaration {
+  optional string name = 1;
+  repeated Declaration members = 2;
+  // Type params not implemented yet
+  // optional TuplePattern params = 3;
+  optional GenericBinding self = 4;
+}
+
+// EXPERIMENTAL MIXIN FEATURE
+message MixDeclaration {
+  optional Expression mixin = 1;
+}
+
 message Declaration {
   oneof kind {
     FunctionDeclaration function = 1;
@@ -399,6 +413,8 @@ message Declaration {
     ImplDeclaration impl = 6;
     AliasDeclaration alias = 7;
     LetDeclaration let = 8;
+    MixinDeclaration mixin = 9;
+    MixDeclaration mix = 10;
   }
 }
 

+ 30 - 0
common/fuzzing/proto_to_carbon.cpp

@@ -721,6 +721,36 @@ static auto DeclarationToCarbon(const Fuzzing::Declaration& declaration,
       break;
     }
 
+    // EXPERIMENTAL MIXIN FEATURE
+    case Fuzzing::Declaration::kMixin: {
+      const auto& mixin_declaration = declaration.mixin();
+      out << "__mixin ";
+      IdentifierToCarbon(mixin_declaration.name(), out);
+
+      // type params are not implemented yet
+      // if (mixin_declaration.has_params()) {
+      //  TuplePatternToCarbon(mixin_declaration.params(), out);
+      //}
+
+      out << "{\n";
+      for (const auto& member : mixin_declaration.members()) {
+        DeclarationToCarbon(member, out);
+        out << "\n";
+      }
+      out << "}";
+      // TODO: need to handle interface.self()?
+      break;
+    }
+
+    // EXPERIMENTAL MIXIN FEATURE
+    case Fuzzing::Declaration::kMix: {
+      const auto& mix_declaration = declaration.mix();
+      out << "__mix ";
+      ExpressionToCarbon(mix_declaration.mixin(), out);
+      out << ";";
+      break;
+    }
+
     case Fuzzing::Declaration::kChoice: {
       const auto& choice = declaration.choice();
       out << "choice ";

+ 2 - 0
explorer/ast/ast_rtti.txt

@@ -16,6 +16,8 @@ abstract class Declaration : AstNode;
   class FunctionDeclaration : Declaration;
   class SelfDeclaration : Declaration;
   class ClassDeclaration : Declaration;
+  class MixinDeclaration : Declaration;
+  class MixDeclaration : Declaration;
   class ChoiceDeclaration : Declaration;
   class VariableDeclaration : Declaration;
   class InterfaceDeclaration : Declaration;

+ 34 - 2
explorer/ast/declaration.cpp

@@ -52,7 +52,22 @@ void Declaration::Print(llvm::raw_ostream& out) const {
       out << "}\n";
       break;
     }
-
+    case DeclarationKind::MixinDeclaration: {
+      const auto& mixin_decl = cast<MixinDeclaration>(*this);
+      PrintID(out);
+      out << "{\n";
+      for (Nonnull<Declaration*> m : mixin_decl.members()) {
+        out << *m;
+      }
+      out << "}\n";
+      break;
+    }
+    case DeclarationKind::MixDeclaration: {
+      const auto& mix_decl = cast<MixDeclaration>(*this);
+      PrintID(out);
+      out << mix_decl.mixin() << ";";
+      break;
+    }
     case DeclarationKind::ChoiceDeclaration: {
       const auto& choice = cast<ChoiceDeclaration>(*this);
       PrintID(out);
@@ -122,7 +137,18 @@ void Declaration::PrintID(llvm::raw_ostream& out) const {
       out << "class " << class_decl.name();
       break;
     }
-
+    case DeclarationKind::MixinDeclaration: {
+      const auto& mixin_decl = cast<MixinDeclaration>(*this);
+      out << "__mixin " << mixin_decl.name();
+      if (mixin_decl.self()->type().kind() != ExpressionKind::TypeTypeLiteral) {
+        out << " for " << mixin_decl.self()->type();
+      }
+      break;
+    }
+    case DeclarationKind::MixDeclaration: {
+      out << "__mix ";
+      break;
+    }
     case DeclarationKind::ChoiceDeclaration: {
       const auto& choice = cast<ChoiceDeclaration>(*this);
       out << "choice " << choice.name();
@@ -161,6 +187,12 @@ auto GetName(const Declaration& declaration)
       return cast<FunctionDeclaration>(declaration).name();
     case DeclarationKind::ClassDeclaration:
       return cast<ClassDeclaration>(declaration).name();
+    case DeclarationKind::MixinDeclaration: {
+      return cast<MixinDeclaration>(declaration).name();
+    }
+    case DeclarationKind::MixDeclaration: {
+      return std::nullopt;
+    }
     case DeclarationKind::ChoiceDeclaration:
       return cast<ChoiceDeclaration>(declaration).name();
     case DeclarationKind::InterfaceDeclaration:

+ 65 - 0
explorer/ast/declaration.h

@@ -26,6 +26,7 @@
 
 namespace Carbon {
 
+class MixinPseudoType;
 class ConstraintType;
 
 // Abstract base class of all AST nodes representing patterns.
@@ -233,6 +234,70 @@ class ClassDeclaration : public Declaration {
   std::vector<Nonnull<Declaration*>> members_;
 };
 
+// EXPERIMENTAL MIXIN FEATURE
+class MixinDeclaration : public Declaration {
+ public:
+  using ImplementsCarbonValueNode = void;
+
+  MixinDeclaration(SourceLocation source_loc, std::string name,
+                   std::optional<Nonnull<TuplePattern*>> params,
+                   Nonnull<GenericBinding*> self,
+                   std::vector<Nonnull<Declaration*>> members)
+      : Declaration(AstNodeKind::MixinDeclaration, source_loc),
+        name_(std::move(name)),
+        params_(std::move(params)),
+        self_(self),
+        members_(std::move(members)) {}
+
+  static auto classof(const AstNode* node) -> bool {
+    return InheritsFromMixinDeclaration(node->kind());
+  }
+
+  auto name() const -> const std::string& { return name_; }
+  auto params() const -> std::optional<Nonnull<const TuplePattern*>> {
+    return params_;
+  }
+  auto params() -> std::optional<Nonnull<TuplePattern*>> { return params_; }
+  auto self() const -> Nonnull<const GenericBinding*> { return self_; }
+  auto self() -> Nonnull<GenericBinding*> { return self_; }
+  auto members() const -> llvm::ArrayRef<Nonnull<Declaration*>> {
+    return members_;
+  }
+
+  auto value_category() const -> ValueCategory { return ValueCategory::Let; }
+
+ private:
+  std::string name_;
+  std::optional<Nonnull<TuplePattern*>> params_;
+  Nonnull<GenericBinding*> self_;
+  std::vector<Nonnull<Declaration*>> members_;
+};
+
+// EXPERIMENTAL MIXIN FEATURE
+class MixDeclaration : public Declaration {
+ public:
+  MixDeclaration(SourceLocation source_loc,
+                 std::optional<Nonnull<Expression*>> mixin_type)
+      : Declaration(AstNodeKind::MixDeclaration, source_loc),
+        mixin_(mixin_type) {}
+
+  static auto classof(const AstNode* node) -> bool {
+    return InheritsFromMixDeclaration(node->kind());
+  }
+
+  auto mixin() const -> const Expression& { return **mixin_; }
+  auto mixin() -> Expression& { return **mixin_; }
+
+  auto mixin_value() const -> const MixinPseudoType& { return *mixin_value_; }
+  void set_mixin_value(Nonnull<const MixinPseudoType*> mixin_value) {
+    mixin_value_ = mixin_value;
+  }
+
+ private:
+  std::optional<Nonnull<Expression*>> mixin_;
+  Nonnull<const MixinPseudoType*> mixin_value_;
+};
+
 class AlternativeSignature : public AstNode {
  public:
   AlternativeSignature(SourceLocation source_loc, std::string name,

+ 23 - 0
explorer/fuzzing/ast_to_proto.cpp

@@ -607,6 +607,29 @@ static auto DeclarationToProto(const Declaration& declaration)
       break;
     }
 
+    case DeclarationKind::MixinDeclaration: {
+      const auto& mixin = cast<MixinDeclaration>(declaration);
+      auto* mixin_proto = declaration_proto.mutable_mixin();
+      mixin_proto->set_name(mixin.name());
+      for (const auto& member : mixin.members()) {
+        *mixin_proto->add_members() = DeclarationToProto(*member);
+      }
+      // Type params not implemented yet
+      // if (mixin.params().has_value()) {
+      //  *mixin_proto->mutable_params() =
+      //      TuplePatternToProto(**mixin.params());
+      //}
+      *mixin_proto->mutable_self() = GenericBindingToProto(*mixin.self());
+      break;
+    }
+
+    case DeclarationKind::MixDeclaration: {
+      const auto& mix = cast<MixDeclaration>(declaration);
+      auto* mix_proto = declaration_proto.mutable_mix();
+      *mix_proto->mutable_mixin() = ExpressionToProto(mix.mixin());
+      break;
+    }
+
     case DeclarationKind::ChoiceDeclaration: {
       const auto& choice = cast<ChoiceDeclaration>(declaration);
       auto* choice_proto = declaration_proto.mutable_choice();

+ 4 - 0
explorer/interpreter/interpreter.cpp

@@ -625,6 +625,7 @@ auto Interpreter::Convert(Nonnull<const Value*> value,
     case Value::Kind::PointerType:
     case Value::Kind::AutoType:
     case Value::Kind::NominalClassType:
+    case Value::Kind::MixinPseudoType:
     case Value::Kind::InterfaceType:
     case Value::Kind::ConstraintType:
     case Value::Kind::ImplWitness:
@@ -640,6 +641,7 @@ auto Interpreter::Convert(Nonnull<const Value*> value,
     case Value::Kind::StringType:
     case Value::Kind::StringValue:
     case Value::Kind::TypeOfClassType:
+    case Value::Kind::TypeOfMixinPseudoType:
     case Value::Kind::TypeOfInterfaceType:
     case Value::Kind::TypeOfConstraintType:
     case Value::Kind::TypeOfChoiceType:
@@ -1824,6 +1826,8 @@ auto Interpreter::StepDeclaration() -> ErrorOr<Success> {
     }
     case DeclarationKind::FunctionDeclaration:
     case DeclarationKind::ClassDeclaration:
+    case DeclarationKind::MixinDeclaration:
+    case DeclarationKind::MixDeclaration:
     case DeclarationKind::ChoiceDeclaration:
     case DeclarationKind::InterfaceDeclaration:
     case DeclarationKind::AssociatedConstantDeclaration:

+ 8 - 0
explorer/interpreter/resolve_control_flow.cpp

@@ -154,6 +154,13 @@ auto ResolveControlFlow(Nonnull<Declaration*> declaration) -> ErrorOr<Success> {
       }
       break;
     }
+    case DeclarationKind::MixinDeclaration: {
+      auto& mixin_decl = cast<MixinDeclaration>(*declaration);
+      for (Nonnull<Declaration*> member : mixin_decl.members()) {
+        CARBON_RETURN_IF_ERROR(ResolveControlFlow(member));
+      }
+      break;
+    }
     case DeclarationKind::InterfaceDeclaration: {
       auto& iface_decl = cast<InterfaceDeclaration>(*declaration);
       for (Nonnull<Declaration*> member : iface_decl.members()) {
@@ -173,6 +180,7 @@ auto ResolveControlFlow(Nonnull<Declaration*> declaration) -> ErrorOr<Success> {
     case DeclarationKind::AssociatedConstantDeclaration:
     case DeclarationKind::SelfDeclaration:
     case DeclarationKind::AliasDeclaration:
+    case DeclarationKind::MixDeclaration:
       // do nothing
       break;
   }

+ 31 - 0
explorer/interpreter/resolve_names.cpp

@@ -46,6 +46,17 @@ static auto AddExposedNames(const Declaration& declaration,
                               StaticScope::NameStatus::KnownButNotDeclared));
       break;
     }
+    case DeclarationKind::MixinDeclaration: {
+      auto& mixin_decl = cast<MixinDeclaration>(declaration);
+      CARBON_RETURN_IF_ERROR(
+          enclosing_scope.Add(mixin_decl.name(), &mixin_decl,
+                              StaticScope::NameStatus::KnownButNotDeclared));
+      break;
+    }
+    case DeclarationKind::MixDeclaration: {
+      // Nothing to do here
+      break;
+    }
     case DeclarationKind::ChoiceDeclaration: {
       auto& choice = cast<ChoiceDeclaration>(declaration);
       CARBON_RETURN_IF_ERROR(
@@ -560,6 +571,26 @@ static auto ResolveNames(Declaration& declaration, StaticScope& enclosing_scope,
           ResolveMemberNames(class_decl.members(), class_scope, bodies));
       break;
     }
+    case DeclarationKind::MixinDeclaration: {
+      auto& mixin_decl = cast<MixinDeclaration>(declaration);
+      StaticScope mixin_scope;
+      mixin_scope.AddParent(&enclosing_scope);
+      enclosing_scope.MarkDeclared(mixin_decl.name());
+      if (mixin_decl.params().has_value()) {
+        CARBON_RETURN_IF_ERROR(
+            ResolveNames(**mixin_decl.params(), mixin_scope));
+      }
+      enclosing_scope.MarkUsable(mixin_decl.name());
+      CARBON_RETURN_IF_ERROR(mixin_scope.Add("Self", mixin_decl.self()));
+      CARBON_RETURN_IF_ERROR(
+          ResolveMemberNames(mixin_decl.members(), mixin_scope, bodies));
+      break;
+    }
+    case DeclarationKind::MixDeclaration: {
+      auto& mix_decl = cast<MixDeclaration>(declaration);
+      CARBON_RETURN_IF_ERROR(ResolveNames(mix_decl.mixin(), enclosing_scope));
+      break;
+    }
     case DeclarationKind::ChoiceDeclaration: {
       auto& choice = cast<ChoiceDeclaration>(declaration);
       StaticScope choice_scope;

+ 2 - 0
explorer/interpreter/resolve_unformed.cpp

@@ -294,6 +294,8 @@ static auto ResolveUnformed(Nonnull<const Declaration*> declaration)
       break;
     }
     case DeclarationKind::ClassDeclaration:
+    case DeclarationKind::MixDeclaration:
+    case DeclarationKind::MixinDeclaration:
     case DeclarationKind::InterfaceDeclaration:
     case DeclarationKind::ImplDeclaration:
     case DeclarationKind::ChoiceDeclaration:

+ 278 - 23
explorer/interpreter/type_checker.cpp

@@ -194,6 +194,7 @@ static auto IsTypeOfType(Nonnull<const Value*> value) -> bool {
     case Value::Kind::PointerType:
     case Value::Kind::StructType:
     case Value::Kind::NominalClassType:
+    case Value::Kind::MixinPseudoType:
     case Value::Kind::ChoiceType:
     case Value::Kind::ContinuationType:
     case Value::Kind::StringType:
@@ -210,6 +211,7 @@ static auto IsTypeOfType(Nonnull<const Value*> value) -> bool {
     case Value::Kind::InterfaceType:
     case Value::Kind::ConstraintType:
     case Value::Kind::TypeOfClassType:
+    case Value::Kind::TypeOfMixinPseudoType:
     case Value::Kind::TypeOfInterfaceType:
     case Value::Kind::TypeOfConstraintType:
     case Value::Kind::TypeOfChoiceType:
@@ -289,6 +291,11 @@ static auto IsType(Nonnull<const Value*> value, bool concrete = false) -> bool {
       // ... is T.(I(Type).V) considered to be a type?
       return IsTypeOfType(&assoc.constant().static_type());
     }
+    case Value::Kind::MixinPseudoType:
+    case Value::Kind::TypeOfMixinPseudoType:
+      // Mixin type is a second-class type that cannot be used
+      // within a type annotation expression.
+      return false;
   }
 }
 
@@ -886,6 +893,9 @@ auto TypeChecker::ArgumentDeduction(
       }
       return Success();
     }
+    case Value::Kind::MixinPseudoType:
+    case Value::Kind::TypeOfMixinPseudoType:
+      CARBON_CHECK(false) << "Type expression must not contain Mixin types";
   }
 }
 
@@ -1149,8 +1159,10 @@ auto TypeChecker::Substitute(
     case Value::Kind::ChoiceType:
     case Value::Kind::ContinuationType:
     case Value::Kind::StringType:
+    case Value::Kind::MixinPseudoType:
       return type;
     case Value::Kind::TypeOfClassType:
+    case Value::Kind::TypeOfMixinPseudoType:
     case Value::Kind::TypeOfInterfaceType:
     case Value::Kind::TypeOfConstraintType:
     case Value::Kind::TypeOfChoiceType:
@@ -1577,19 +1589,21 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
         }
         case Value::Kind::NominalClassType: {
           const auto& t_class = cast<NominalClassType>(object_type);
-          if (std::optional<Nonnull<const Declaration*>> member = FindMember(
-                  access.member_name(), t_class.declaration().members());
-              member.has_value()) {
+          if (auto type_member = FindMixedMemberAndType(
+                  access.member_name(), t_class.declaration().members(),
+                  &t_class);
+              type_member.has_value()) {
+            auto [member_type, member] = type_member.value();
             Nonnull<const Value*> field_type =
-                Substitute(t_class.type_args(), &(*member)->static_type());
-            access.set_member(Member(member.value()));
+                Substitute(t_class.type_args(), member_type);
+            access.set_member(Member(member));
             access.set_static_type(field_type);
-            switch ((*member)->kind()) {
+            switch (member->kind()) {
               case DeclarationKind::VariableDeclaration:
                 access.set_value_category(access.object().value_category());
                 break;
               case DeclarationKind::FunctionDeclaration: {
-                auto func_decl = cast<FunctionDeclaration>(*member);
+                auto func_decl = cast<FunctionDeclaration>(member);
                 if (func_decl->is_method() && func_decl->me_pattern().kind() ==
                                                   PatternKind::AddrPattern) {
                   access.set_is_field_addr_me_method();
@@ -1764,19 +1778,20 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
             case Value::Kind::NominalClassType: {
               const NominalClassType& class_type =
                   cast<NominalClassType>(*type);
-              if (std::optional<Nonnull<const Declaration*>> member =
-                      FindMember(access.member_name(),
-                                 class_type.declaration().members());
-                  member.has_value()) {
-                access.set_member(Member(member.value()));
-                switch ((*member)->kind()) {
+              if (auto type_member = FindMixedMemberAndType(
+                      access.member_name(), class_type.declaration().members(),
+                      &class_type);
+                  type_member.has_value()) {
+                auto [member_type, member] = type_member.value();
+                access.set_member(Member(member));
+                switch (member->kind()) {
                   case DeclarationKind::FunctionDeclaration: {
                     const auto& func = cast<FunctionDeclaration>(*member);
-                    if (func->is_method()) {
+                    if (func.is_method()) {
                       break;
                     }
                     Nonnull<const Value*> field_type = Substitute(
-                        class_type.type_args(), &(*member)->static_type());
+                        class_type.type_args(), &member->static_type());
                     access.set_static_type(field_type);
                     access.set_value_category(ValueCategory::Let);
                     return Success();
@@ -1785,7 +1800,7 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
                     break;
                 }
                 access.set_static_type(
-                    arena_->New<TypeOfMemberName>(Member(*member)));
+                    arena_->New<TypeOfMemberName>(Member(member)));
                 access.set_value_category(ValueCategory::Let);
                 return Success();
               } else {
@@ -3431,8 +3446,13 @@ auto TypeChecker::TypeCheckClassDeclaration(
   if (trace_stream_) {
     **trace_stream_ << class_scope;
   }
+  auto [it, inserted] =
+      collected_members_.insert({class_decl, CollectedMembersMap()});
+  CARBON_CHECK(inserted) << "Adding class " << class_decl->name()
+                         << " to collected_members_ must not fail";
   for (Nonnull<Declaration*> m : class_decl->members()) {
-    CARBON_RETURN_IF_ERROR(TypeCheckDeclaration(m, class_scope));
+    CARBON_RETURN_IF_ERROR(TypeCheckDeclaration(m, class_scope, class_decl));
+    CARBON_RETURN_IF_ERROR(CollectMember(class_decl, m));
   }
   if (trace_stream_) {
     **trace_stream_ << "** finished checking class " << class_decl->name()
@@ -3441,6 +3461,132 @@ auto TypeChecker::TypeCheckClassDeclaration(
   return Success();
 }
 
+// EXPERIMENTAL MIXIN FEATURE
+auto TypeChecker::DeclareMixinDeclaration(Nonnull<MixinDeclaration*> mixin_decl,
+                                          const ScopeInfo& scope_info)
+    -> ErrorOr<Success> {
+  if (trace_stream_) {
+    **trace_stream_ << "** declaring mixin " << mixin_decl->name() << "\n";
+  }
+  ImplScope mixin_scope;
+  mixin_scope.AddParent(scope_info.innermost_scope);
+
+  if (mixin_decl->params().has_value()) {
+    CARBON_RETURN_IF_ERROR(TypeCheckPattern(*mixin_decl->params(), std::nullopt,
+                                            mixin_scope, ValueCategory::Let));
+    if (trace_stream_) {
+      **trace_stream_ << mixin_scope;
+    }
+
+    Nonnull<ParameterizedEntityName*> param_name =
+        arena_->New<ParameterizedEntityName>(mixin_decl, *mixin_decl->params());
+    SetConstantValue(mixin_decl, param_name);
+    mixin_decl->set_static_type(
+        arena_->New<TypeOfParameterizedEntityName>(param_name));
+  } else {
+    Nonnull<MixinPseudoType*> mixin_type =
+        arena_->New<MixinPseudoType>(mixin_decl);
+    SetConstantValue(mixin_decl, mixin_type);
+    mixin_decl->set_static_type(arena_->New<TypeOfMixinPseudoType>(mixin_type));
+  }
+
+  // Process the Self parameter.
+  CARBON_RETURN_IF_ERROR(TypeCheckPattern(mixin_decl->self(), std::nullopt,
+                                          mixin_scope, ValueCategory::Let));
+
+  ScopeInfo mixin_scope_info = ScopeInfo::ForNonClassScope(&mixin_scope);
+  for (Nonnull<Declaration*> m : mixin_decl->members()) {
+    CARBON_RETURN_IF_ERROR(DeclareDeclaration(m, mixin_scope_info));
+  }
+
+  if (trace_stream_) {
+    **trace_stream_ << "** finished declaring mixin " << mixin_decl->name()
+                    << "\n";
+  }
+  return Success();
+}
+
+// EXPERIMENTAL MIXIN FEATURE
+/*
+** Checks to see if mixin_decl is already within collected_members_. If it is,
+** then the mixin has already been type checked before either while type
+** checking a previous mix declaration or while type checking the original mixin
+** declaration. If not, then every member declaration is type checked and then
+** added to collected_members_ under the mixin_decl key.
+*/
+auto TypeChecker::TypeCheckMixinDeclaration(
+    Nonnull<const MixinDeclaration*> mixin_decl, const ImplScope& impl_scope)
+    -> ErrorOr<Success> {
+  auto [it, inserted] =
+      collected_members_.insert({mixin_decl, CollectedMembersMap()});
+  if (!inserted) {
+    // This declaration has already been type checked before
+    if (trace_stream_) {
+      **trace_stream_ << "** skipped checking mixin " << mixin_decl->name()
+                      << "\n";
+    }
+    return Success();
+  }
+  if (trace_stream_) {
+    **trace_stream_ << "** checking mixin " << mixin_decl->name() << "\n";
+  }
+  ImplScope mixin_scope;
+  mixin_scope.AddParent(&impl_scope);
+  if (mixin_decl->params().has_value()) {
+    BringPatternImplsIntoScope(*mixin_decl->params(), mixin_scope);
+  }
+  if (trace_stream_) {
+    **trace_stream_ << mixin_scope;
+  }
+  for (Nonnull<Declaration*> m : mixin_decl->members()) {
+    CARBON_RETURN_IF_ERROR(TypeCheckDeclaration(m, mixin_scope, mixin_decl));
+    CARBON_RETURN_IF_ERROR(CollectMember(mixin_decl, m));
+  }
+  if (trace_stream_) {
+    **trace_stream_ << "** finished checking mixin " << mixin_decl->name()
+                    << "\n";
+  }
+  return Success();
+}
+
+// EXPERIMENTAL MIXIN FEATURE
+/*
+** Type checks the mixin mentioned in the mix declaration.
+** TypeCheckMixinDeclaration ensures that the members of that mixin are
+** available in collected_members_. The mixin members are then collected as
+** members of the enclosing class or mixin declaration.
+*/
+auto TypeChecker::TypeCheckMixDeclaration(
+    Nonnull<MixDeclaration*> mix_decl, const ImplScope& impl_scope,
+    std::optional<Nonnull<const Declaration*>> enclosing_decl)
+    -> ErrorOr<Success> {
+  if (trace_stream_) {
+    **trace_stream_ << "** checking " << *mix_decl << "\n";
+  }
+  // TODO(darshal): Check if the imports (interface mentioned in the 'for'
+  // clause) of the mixin being mixed are being impl'd in the enclosed
+  // class/mixin declaration This raises the question of how to handle impl
+  // declarations in mixin declarations
+
+  CARBON_CHECK(enclosing_decl.has_value());
+  Nonnull<const Declaration*> encl_decl = enclosing_decl.value();
+  auto& mixin_decl = mix_decl->mixin_value().declaration();
+  CARBON_RETURN_IF_ERROR(TypeCheckMixinDeclaration(&mixin_decl, impl_scope));
+  CollectedMembersMap& mix_members = FindCollectedMembers(&mixin_decl);
+
+  // Merge members collected in the enclosing declaration with the members
+  // collected for the mixin declaration associated with the mix declaration
+  for (auto [mix_member_name, mix_member] : mix_members) {
+    CARBON_RETURN_IF_ERROR(CollectMember(encl_decl, mix_member));
+  }
+
+  if (trace_stream_) {
+    **trace_stream_ << "** finished checking " << *mix_decl << "\n";
+  }
+
+  return Success();
+}
+
 auto TypeChecker::DeclareInterfaceDeclaration(
     Nonnull<InterfaceDeclaration*> iface_decl, const ScopeInfo& scope_info)
     -> ErrorOr<Success> {
@@ -3521,7 +3667,7 @@ auto TypeChecker::TypeCheckInterfaceDeclaration(
     **trace_stream_ << iface_scope;
   }
   for (Nonnull<Declaration*> m : iface_decl->members()) {
-    CARBON_RETURN_IF_ERROR(TypeCheckDeclaration(m, iface_scope));
+    CARBON_RETURN_IF_ERROR(TypeCheckDeclaration(m, iface_scope, iface_decl));
   }
   if (trace_stream_) {
     **trace_stream_ << "** finished checking interface " << iface_decl->name()
@@ -3813,7 +3959,7 @@ auto TypeChecker::TypeCheckImplDeclaration(Nonnull<ImplDeclaration*> impl_decl,
     BringAssociatedConstantsIntoScope(constraint, self, result.interface,
                                       member_scope);
 
-    CARBON_RETURN_IF_ERROR(TypeCheckDeclaration(m, member_scope));
+    CARBON_RETURN_IF_ERROR(TypeCheckDeclaration(m, member_scope, impl_decl));
   }
   if (trace_stream_) {
     **trace_stream_ << "finished checking impl\n";
@@ -3884,6 +4030,8 @@ static bool IsValidTypeForAliasTarget(Nonnull<const Value*> type) {
     case Value::Kind::BoolValue:
     case Value::Kind::StructValue:
     case Value::Kind::NominalClassValue:
+    case Value::Kind::MixinPseudoType:
+    case Value::Kind::TypeOfMixinPseudoType:
     case Value::Kind::AlternativeValue:
     case Value::Kind::TupleValue:
     case Value::Kind::ImplWitness:
@@ -3955,7 +4103,8 @@ auto TypeChecker::TypeCheck(AST& ast) -> ErrorOr<Success> {
         DeclareDeclaration(declaration, top_level_scope_info));
   }
   for (Nonnull<Declaration*> decl : ast.declarations) {
-    CARBON_RETURN_IF_ERROR(TypeCheckDeclaration(decl, impl_scope));
+    CARBON_RETURN_IF_ERROR(
+        TypeCheckDeclaration(decl, impl_scope, std::nullopt));
     // Check to see if this declaration is a builtin.
     // TODO: Only do this when type-checking the prelude.
     builtins_.Register(decl);
@@ -3964,8 +4113,9 @@ auto TypeChecker::TypeCheck(AST& ast) -> ErrorOr<Success> {
   return Success();
 }
 
-auto TypeChecker::TypeCheckDeclaration(Nonnull<Declaration*> d,
-                                       const ImplScope& impl_scope)
+auto TypeChecker::TypeCheckDeclaration(
+    Nonnull<Declaration*> d, const ImplScope& impl_scope,
+    std::optional<Nonnull<const Declaration*>> enclosing_decl)
     -> ErrorOr<Success> {
   if (trace_stream_) {
     **trace_stream_ << "checking " << DeclarationKindName(d->kind()) << "\n";
@@ -3989,6 +4139,16 @@ auto TypeChecker::TypeCheckDeclaration(Nonnull<Declaration*> d,
       CARBON_RETURN_IF_ERROR(
           TypeCheckClassDeclaration(&cast<ClassDeclaration>(*d), impl_scope));
       return Success();
+    case DeclarationKind::MixinDeclaration: {
+      CARBON_RETURN_IF_ERROR(
+          TypeCheckMixinDeclaration(&cast<MixinDeclaration>(*d), impl_scope));
+      return Success();
+    }
+    case DeclarationKind::MixDeclaration: {
+      CARBON_RETURN_IF_ERROR(TypeCheckMixDeclaration(
+          &cast<MixDeclaration>(*d), impl_scope, enclosing_decl));
+      return Success();
+    }
     case DeclarationKind::ChoiceDeclaration:
       CARBON_RETURN_IF_ERROR(
           TypeCheckChoiceDeclaration(&cast<ChoiceDeclaration>(*d), impl_scope));
@@ -4052,7 +4212,19 @@ auto TypeChecker::DeclareDeclaration(Nonnull<Declaration*> d,
       CARBON_RETURN_IF_ERROR(DeclareClassDeclaration(&class_decl, scope_info));
       break;
     }
-
+    case DeclarationKind::MixinDeclaration: {
+      auto& mixin_decl = cast<MixinDeclaration>(*d);
+      CARBON_RETURN_IF_ERROR(DeclareMixinDeclaration(&mixin_decl, scope_info));
+      break;
+    }
+    case DeclarationKind::MixDeclaration: {
+      auto& mix_decl = cast<MixDeclaration>(*d);
+      CARBON_ASSIGN_OR_RETURN(
+          Nonnull<const Value*> mixin,
+          InterpExp(&mix_decl.mixin(), arena_, trace_stream_));
+      mix_decl.set_mixin_value(cast<MixinPseudoType>(mixin));
+      break;
+    }
     case DeclarationKind::ChoiceDeclaration: {
       auto& choice = cast<ChoiceDeclaration>(*d);
       CARBON_RETURN_IF_ERROR(DeclareChoiceDeclaration(&choice, scope_info));
@@ -4118,4 +4290,87 @@ void TypeChecker::PrintConstants(llvm::raw_ostream& out) {
   }
 }
 
+auto TypeChecker::FindMixedMemberAndType(
+    const std::string_view& name, llvm::ArrayRef<Nonnull<Declaration*>> members,
+    const Nonnull<const Value*> enclosing_type)
+    -> std::optional<
+        std::pair<Nonnull<const Value*>, Nonnull<const Declaration*>>> {
+  for (Nonnull<const Declaration*> member : members) {
+    if (llvm::isa<MixDeclaration>(member)) {
+      const auto& mix_decl = cast<MixDeclaration>(*member);
+      Nonnull<const MixinPseudoType*> mixin = &mix_decl.mixin_value();
+      const auto res =
+          FindMixedMemberAndType(name, mixin->declaration().members(), mixin);
+      if (res.has_value()) {
+        if (isa<NominalClassType>(enclosing_type)) {
+          BindingMap temp_map;
+          temp_map[mixin->declaration().self()] = enclosing_type;
+          const auto mix_member_type = Substitute(temp_map, res.value().first);
+          return std::make_pair(mix_member_type, res.value().second);
+        } else {
+          return res;
+        }
+      }
+
+    } else if (std::optional<std::string_view> mem_name = GetName(*member);
+               mem_name.has_value()) {
+      if (*mem_name == name) {
+        return std::make_pair(&member->static_type(), member);
+      }
+    }
+  }
+
+  return std::nullopt;
+}
+
+auto TypeChecker::CollectMember(Nonnull<const Declaration*> enclosing_decl,
+                                Nonnull<const Declaration*> member_decl)
+    -> ErrorOr<Success> {
+  CARBON_CHECK(isa<MixinDeclaration>(enclosing_decl) ||
+               isa<ClassDeclaration>(enclosing_decl))
+      << "Can't collect members for " << *enclosing_decl;
+  auto member_name = GetName(*member_decl);
+  if (!member_name.has_value()) {
+    // No need to collect members without a name
+    return Success();
+  }
+  auto encl_decl_name = GetName(*enclosing_decl);
+  CARBON_CHECK(encl_decl_name.has_value());
+  auto enclosing_decl_name = encl_decl_name.value();
+  auto enclosing_decl_loc = enclosing_decl->source_loc();
+  CollectedMembersMap& encl_members = FindCollectedMembers(enclosing_decl);
+  auto [it, inserted] = encl_members.insert({member_name.value(), member_decl});
+  if (!inserted) {
+    if (member_decl == it->second) {
+      return CompilationError(enclosing_decl_loc)
+             << "Member named " << member_name.value() << " (declared at "
+             << member_decl->source_loc() << ")"
+             << " is being mixed multiple times into " << enclosing_decl_name;
+    } else {
+      return CompilationError(enclosing_decl_loc)
+             << "Member named " << member_name.value() << " (declared at "
+             << member_decl->source_loc() << ") cannot be mixed into "
+             << enclosing_decl_name
+             << " because it clashes with an existing member"
+             << " with the same name (declared at " << it->second->source_loc()
+             << ") ";
+    }
+  }
+  return Success();
+}
+
+auto TypeChecker::FindCollectedMembers(Nonnull<const Declaration*> decl)
+    -> CollectedMembersMap& {
+  switch (decl->kind()) {
+    case DeclarationKind::MixinDeclaration:
+    case DeclarationKind::ClassDeclaration: {
+      auto it = collected_members_.find(decl);
+      CARBON_CHECK(it != collected_members_.end());
+      return it->second;
+    }
+    default:
+      CARBON_FATAL() << "Can't collect members for " << *decl;
+  }
+}
+
 }  // namespace Carbon

+ 54 - 2
explorer/interpreter/type_checker.h

@@ -19,6 +19,12 @@
 
 namespace Carbon {
 
+using CollectedMembersMap =
+    std::unordered_map<std::string_view, Nonnull<const Declaration*>>;
+
+using GlobalMembersMap =
+    std::unordered_map<Nonnull<const Declaration*>, CollectedMembersMap>;
+
 class TypeChecker {
  public:
   explicit TypeChecker(Nonnull<Arena*> arena,
@@ -64,6 +70,20 @@ class TypeChecker {
                  SourceLocation source_loc) const
       -> std::optional<Nonnull<Expression*>>;
 
+  /*
+  ** Finds the direct or indirect member of a class or mixin by its name and
+  ** returns the member's declaration and type. Indirect members are members of
+  ** mixins that are mixed by member mix declarations. If the member is an
+  ** indirect member from a mix declaration, then the Self type variable within
+  ** the member's type is substituted with the type of the enclosing declaration
+  ** containing the mix declaration.
+  */
+  auto FindMixedMemberAndType(const std::string_view& name,
+                              llvm::ArrayRef<Nonnull<Declaration*>> members,
+                              const Nonnull<const Value*> enclosing_type)
+      -> std::optional<
+          std::pair<Nonnull<const Value*>, Nonnull<const Declaration*>>>;
+
   // Given the witnesses for the components of a constraint, form a witness for
   // the constraint.
   auto MakeConstraintWitness(
@@ -179,6 +199,9 @@ class TypeChecker {
   auto DeclareClassDeclaration(Nonnull<ClassDeclaration*> class_decl,
                                const ScopeInfo& scope_info) -> ErrorOr<Success>;
 
+  auto DeclareMixinDeclaration(Nonnull<MixinDeclaration*> mixin_decl,
+                               const ScopeInfo& scope_info) -> ErrorOr<Success>;
+
   auto DeclareInterfaceDeclaration(Nonnull<InterfaceDeclaration*> iface_decl,
                                    const ScopeInfo& scope_info)
       -> ErrorOr<Success>;
@@ -246,8 +269,10 @@ class TypeChecker {
   // declaration, such as the body of a function.
   // Dispatches to one of the following functions.
   // Assumes that DeclareDeclaration has already been invoked on `d`.
-  auto TypeCheckDeclaration(Nonnull<Declaration*> d,
-                            const ImplScope& impl_scope) -> ErrorOr<Success>;
+  auto TypeCheckDeclaration(
+      Nonnull<Declaration*> d, const ImplScope& impl_scope,
+      std::optional<Nonnull<const Declaration*>> enclosing_decl)
+      -> ErrorOr<Success>;
 
   // Type check the body of the function.
   auto TypeCheckFunctionDeclaration(Nonnull<FunctionDeclaration*> f,
@@ -259,6 +284,16 @@ class TypeChecker {
                                  const ImplScope& impl_scope)
       -> ErrorOr<Success>;
 
+  // Type check all the members of the mixin.
+  auto TypeCheckMixinDeclaration(Nonnull<const MixinDeclaration*> mixin_decl,
+                                 const ImplScope& impl_scope)
+      -> ErrorOr<Success>;
+
+  auto TypeCheckMixDeclaration(
+      Nonnull<MixDeclaration*> mix_decl, const ImplScope& impl_scope,
+      std::optional<Nonnull<const Declaration*>> enclosing_decl)
+      -> ErrorOr<Success>;
+
   // Type check all the members of the interface.
   auto TypeCheckInterfaceDeclaration(Nonnull<InterfaceDeclaration*> iface_decl,
                                      const ImplScope& impl_scope)
@@ -404,10 +439,27 @@ class TypeChecker {
 
   void PrintConstants(llvm::raw_ostream& out);
 
+  /*
+  ** Adds a member of a declaration to collected_members_
+  */
+  auto CollectMember(Nonnull<const Declaration*> enclosing_decl,
+                     Nonnull<const Declaration*> member_decl)
+      -> ErrorOr<Success>;
+
+  /*
+  ** Fetches all direct and indirect members of a class or mixin declaration
+  ** stored within collected_members_
+  */
+  auto FindCollectedMembers(Nonnull<const Declaration*> decl)
+      -> CollectedMembersMap&;
+
   Nonnull<Arena*> arena_;
   std::set<ValueNodeView> constants_;
   Builtins builtins_;
 
+  // Maps a mixin/class declaration to all of its direct and indirect members.
+  GlobalMembersMap collected_members_;
+
   std::optional<Nonnull<llvm::raw_ostream*>> trace_stream_;
 };
 

+ 63 - 0
explorer/interpreter/value.cpp

@@ -400,6 +400,20 @@ void Value::Print(llvm::raw_ostream& out) const {
       }
       break;
     }
+    case Value::Kind::MixinPseudoType: {
+      const auto& mixin_type = cast<MixinPseudoType>(*this);
+      out << "mixin ";
+      PrintNameWithBindings(out, &mixin_type.declaration(), mixin_type.args());
+      if (!mixin_type.witnesses().empty()) {
+        out << " witnesses ";
+        llvm::ListSeparator sep;
+        for (const auto& [impl_bind, witness] : mixin_type.witnesses()) {
+          out << sep << *witness;
+        }
+      }
+      // TODO: print the import interface
+      break;
+    }
     case Value::Kind::InterfaceType: {
       const auto& iface_type = cast<InterfaceType>(*this);
       out << "interface ";
@@ -491,6 +505,14 @@ void Value::Print(llvm::raw_ostream& out) const {
     case Value::Kind::TypeOfClassType:
       out << "typeof(" << cast<TypeOfClassType>(*this).class_type() << ")";
       break;
+    case Value::Kind::TypeOfMixinPseudoType:
+      out << "typeof("
+          << cast<TypeOfMixinPseudoType>(*this)
+                 .mixin_type()
+                 .declaration()
+                 .name()
+          << ")";
+      break;
     case Value::Kind::TypeOfInterfaceType:
       out << "typeof("
           << cast<TypeOfInterfaceType>(*this)
@@ -731,6 +753,8 @@ auto TypeEqual(Nonnull<const Value*> t1, Nonnull<const Value*> t2,
     case Value::Kind::MemberName:
     case Value::Kind::TypeOfParameterizedEntityName:
     case Value::Kind::TypeOfMemberName:
+    case Value::Kind::MixinPseudoType:
+    case Value::Kind::TypeOfMixinPseudoType:
       CARBON_FATAL() << "TypeEqual used to compare non-type values\n"
                      << *t1 << "\n"
                      << *t2;
@@ -830,6 +854,7 @@ auto ValueStructurallyEqual(
     case Value::Kind::AutoType:
     case Value::Kind::StructType:
     case Value::Kind::NominalClassType:
+    case Value::Kind::MixinPseudoType:
     case Value::Kind::InterfaceType:
     case Value::Kind::ConstraintType:
     case Value::Kind::ImplWitness:
@@ -839,6 +864,7 @@ auto ValueStructurallyEqual(
     case Value::Kind::VariableType:
     case Value::Kind::StringType:
     case Value::Kind::TypeOfClassType:
+    case Value::Kind::TypeOfMixinPseudoType:
     case Value::Kind::TypeOfInterfaceType:
     case Value::Kind::TypeOfConstraintType:
     case Value::Kind::TypeOfChoiceType:
@@ -950,6 +976,43 @@ auto NominalClassType::FindFunction(std::string_view name) const
     -> std::optional<Nonnull<const FunctionValue*>> {
   for (const auto& member : declaration().members()) {
     switch (member->kind()) {
+      case DeclarationKind::MixDeclaration: {
+        const auto& mix_decl = cast<MixDeclaration>(*member);
+        Nonnull<const MixinPseudoType*> mixin = &mix_decl.mixin_value();
+        const auto res = mixin->FindFunction(name);
+        if (res.has_value()) {
+          return res;
+        }
+        break;
+      }
+      case DeclarationKind::FunctionDeclaration: {
+        const auto& fun = cast<FunctionDeclaration>(*member);
+        if (fun.name() == name) {
+          return &cast<FunctionValue>(**fun.constant_value());
+        }
+        break;
+      }
+      default:
+        break;
+    }
+  }
+  return std::nullopt;
+}
+
+// TODO: Find out a way to remove code duplication
+auto MixinPseudoType::FindFunction(const std::string_view& name) const
+    -> std::optional<Nonnull<const FunctionValue*>> {
+  for (const auto& member : declaration().members()) {
+    switch (member->kind()) {
+      case DeclarationKind::MixDeclaration: {
+        const auto& mix_decl = cast<MixDeclaration>(*member);
+        Nonnull<const MixinPseudoType*> mixin = &mix_decl.mixin_value();
+        const auto res = mixin->FindFunction(name);
+        if (res.has_value()) {
+          return res;
+        }
+        break;
+      }
       case DeclarationKind::FunctionDeclaration: {
         const auto& fun = cast<FunctionDeclaration>(*member);
         if (fun.name() == name) {

+ 52 - 0
explorer/interpreter/value.h

@@ -58,6 +58,7 @@ class Value {
     AutoType,
     StructType,
     NominalClassType,
+    MixinPseudoType,
     InterfaceType,
     ConstraintType,
     ChoiceType,
@@ -73,6 +74,7 @@ class Value {
     StringType,
     StringValue,
     TypeOfClassType,
+    TypeOfMixinPseudoType,
     TypeOfInterfaceType,
     TypeOfConstraintType,
     TypeOfChoiceType,
@@ -630,6 +632,41 @@ class NominalClassType : public Value {
   Nonnull<const Bindings*> bindings_ = Bindings::None();
 };
 
+class MixinPseudoType : public Value {
+ public:
+  explicit MixinPseudoType(Nonnull<const MixinDeclaration*> declaration)
+      : Value(Kind::MixinPseudoType), declaration_(declaration) {
+    CARBON_CHECK(!declaration->params().has_value())
+        << "missing arguments for parameterized mixin type";
+  }
+  explicit MixinPseudoType(Nonnull<const MixinDeclaration*> declaration,
+                           Nonnull<const Bindings*> bindings)
+      : Value(Kind::MixinPseudoType),
+        declaration_(declaration),
+        bindings_(bindings) {}
+
+  static auto classof(const Value* value) -> bool {
+    return value->kind() == Kind::MixinPseudoType;
+  }
+
+  auto declaration() const -> const MixinDeclaration& { return *declaration_; }
+
+  auto bindings() const -> const Bindings& { return *bindings_; }
+
+  auto args() const -> const BindingMap& { return bindings_->args(); }
+
+  auto witnesses() const -> const ImplWitnessMap& {
+    return bindings_->witnesses();
+  }
+
+  auto FindFunction(const std::string_view& name) const
+      -> std::optional<Nonnull<const FunctionValue*>>;
+
+ private:
+  Nonnull<const MixinDeclaration*> declaration_;
+  Nonnull<const Bindings*> bindings_ = Bindings::None();
+};
+
 // Return the declaration of the member with the given name.
 auto FindMember(std::string_view name,
                 llvm::ArrayRef<Nonnull<Declaration*>> members)
@@ -1097,6 +1134,21 @@ class TypeOfClassType : public Value {
   Nonnull<const NominalClassType*> class_type_;
 };
 
+class TypeOfMixinPseudoType : public Value {
+ public:
+  explicit TypeOfMixinPseudoType(Nonnull<const MixinPseudoType*> class_type)
+      : Value(Kind::TypeOfMixinPseudoType), mixin_type_(class_type) {}
+
+  static auto classof(const Value* value) -> bool {
+    return value->kind() == Kind::TypeOfMixinPseudoType;
+  }
+
+  auto mixin_type() const -> const MixinPseudoType& { return *mixin_type_; }
+
+ private:
+  Nonnull<const MixinPseudoType*> mixin_type_;
+};
+
 class TypeOfInterfaceType : public Value {
  public:
   explicit TypeOfInterfaceType(Nonnull<const InterfaceType*> iface_type)

+ 4 - 0
explorer/syntax/lexer.lpp

@@ -85,6 +85,8 @@ LET                  "let"
 LIBRARY              "library"
 MATCH                "match"
 MINUS                "-"
+MIX                  "__mix"
+MIXIN                "__mixin"
 NOT                  "not"
 OR                   "or"
 PACKAGE              "package"
@@ -187,6 +189,8 @@ operand_start         [(A-Za-z0-9_\"]
 {LIBRARY}             { return CARBON_SIMPLE_TOKEN(LIBRARY);             }
 {MATCH}               { return CARBON_SIMPLE_TOKEN(MATCH);               }
 {MINUS}               { return CARBON_SIMPLE_TOKEN(MINUS);               }
+{MIXIN}               { return CARBON_SIMPLE_TOKEN(MIXIN);               }
+{MIX}                 { return CARBON_SIMPLE_TOKEN(MIX);                 }
 {NOT}                 { return CARBON_SIMPLE_TOKEN(NOT);                 }
 {OR}                  { return CARBON_SIMPLE_TOKEN(OR);                  }
 {PACKAGE}             { return CARBON_SIMPLE_TOKEN(PACKAGE);             }

+ 58 - 1
explorer/syntax/parser.ypp

@@ -108,8 +108,11 @@
 %type <std::optional<Nonnull<Expression*>>> class_declaration_extends
 %type <Nonnull<Declaration*>> declaration
 %type <Nonnull<FunctionDeclaration*>> function_declaration
+%type <Nonnull<MixDeclaration*>> mix_declaration
 %type <Nonnull<AliasDeclaration*>> alias_declaration
 %type <std::vector<Nonnull<Declaration*>>> declaration_list
+%type <std::vector<Nonnull<Declaration*>>> class_body
+%type <std::vector<Nonnull<Declaration*>>> mixin_body
 %type <std::vector<Nonnull<Declaration*>>> interface_body
 %type <std::vector<Nonnull<Declaration*>>> impl_body
 %type <Nonnull<Statement*>> statement
@@ -160,6 +163,7 @@
 %type <Nonnull<Expression*>> statement_expression
 %type <Nonnull<Expression*>> if_expression
 %type <Nonnull<Expression*>> expression
+%type <Nonnull<Expression*>> mixin_import
 %type <Nonnull<GenericBinding*>> generic_binding
 %type <Nonnull<Pattern*>> deduced_param
 %type <std::vector<Nonnull<AstNode*>>> deduced_params
@@ -249,6 +253,8 @@
   LIBRARY
   MATCH
   MINUS
+  MIX
+  MIXIN
   NOT
   OR
   PACKAGE
@@ -1066,6 +1072,10 @@ variable_declaration: identifier COLON pattern
 alias_declaration: ALIAS identifier EQUAL expression SEMICOLON
     { $$ = arena->New<AliasDeclaration>(context.source_loc(), $2, $4); }
 ;
+// EXPERIMENTAL MIXIN FEATURE
+mix_declaration: MIX expression SEMICOLON
+    { $$ = arena->New<MixDeclaration>(context.source_loc(), $2); }
+;
 alternative:
   identifier tuple
     { $$ = arena->New<AlternativeSignature>(context.source_loc(), $1, $2); }
@@ -1099,6 +1109,17 @@ type_params:
 | tuple_pattern
     { $$ = $1; }
 ;
+// EXPERIMENTAL MIXIN FEATURE
+mixin_import:
+  // Empty
+    { $$ = arena->New<TypeTypeLiteral>(context.source_loc()); }
+| FOR expression
+    {
+      context.RecordSyntaxError("'for' not supported currently");
+      YYERROR;
+      // $$ = $2;
+    }
+;
 class_declaration_extensibility:
   // Empty
     { $$ = Carbon::ClassExtensibility::None; }
@@ -1116,12 +1137,19 @@ class_declaration_extends:
 declaration:
   function_declaration
     { $$ = $1; }
-| class_declaration_extensibility CLASS identifier type_params class_declaration_extends LEFT_CURLY_BRACE declaration_list RIGHT_CURLY_BRACE
+| class_declaration_extensibility CLASS identifier type_params class_declaration_extends LEFT_CURLY_BRACE class_body RIGHT_CURLY_BRACE
     {
       $$ = arena->New<ClassDeclaration>(
           context.source_loc(), $3,
           arena->New<SelfDeclaration>(context.source_loc()), $1, $4, $5, $7);
     }
+| MIXIN identifier type_params mixin_import LEFT_CURLY_BRACE mixin_body RIGHT_CURLY_BRACE
+    {
+      // EXPERIMENTAL MIXN FEATURE
+      auto self =
+          arena -> New<GenericBinding>(context.source_loc(), "Self", $4);
+      $$ = arena->New<MixinDeclaration>(context.source_loc(), $2, $3, self, $6);
+    }
 | CHOICE identifier type_params LEFT_CURLY_BRACE alternative_list RIGHT_CURLY_BRACE
     { $$ = arena->New<ChoiceDeclaration>(context.source_loc(), $2, $3, $5); }
 | VAR variable_declaration SEMICOLON
@@ -1184,6 +1212,35 @@ declaration_list:
       $$.push_back(Nonnull<Declaration*>($2));
     }
 ;
+class_body:
+  // Empty
+    { $$ = {}; }
+| class_body declaration
+    {
+      $$ = $1;
+      $$.push_back(Nonnull<Declaration*>($2));
+    }
+| class_body mix_declaration
+    {
+      $$ = $1;
+      $$.push_back(Nonnull<Declaration*>($2));
+    }
+;
+// EXPERIMENTAL MIXIN FEATURE
+mixin_body:
+  // Empty
+    { $$ = {}; }
+| mixin_body function_declaration
+    {
+      $$ = $1;
+      $$.push_back(Nonnull<Declaration*>($2));
+    }
+| mixin_body mix_declaration
+    {
+      $$ = $1;
+      $$.push_back(Nonnull<Declaration*>($2));
+    }
+;
 interface_body:
   // Empty
     { $$ = {}; }

+ 38 - 0
explorer/testdata/mixin/fail-circular-mixing.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
+package ExplorerTest api;
+
+__mixin M1 {
+  fn F1[me: Self](x: Self) -> Self{
+     return x;
+  }
+// CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/mixin/fail-circular-mixing.carbon:[[@LINE+1]]: 'M3' has not been declared yet
+  __mix M3;
+}
+
+__mixin M2 {
+  fn F2() {
+  }
+  __mix M1;
+}
+
+__mixin M3 {
+  __mix M2;
+  fn F3() {
+  }
+}
+
+class C {
+  __mix M2;
+}
+
+fn Main() -> i32 {
+  return 0;
+}

+ 27 - 0
explorer/testdata/mixin/fail-field-member-name-clash.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
+// CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/mixin/fail-field-member-name-clash.carbon:23: Member named F (declared at {{.*}}/explorer/testdata/mixin/fail-field-member-name-clash.carbon:17) cannot be mixed into C because it clashes with an existing member with the same name (declared at {{.*}}/explorer/testdata/mixin/fail-field-member-name-clash.carbon:21)
+
+package ExplorerTest api;
+
+__mixin M {
+  fn F[me: Self](x: Self) -> Self{
+     return x;
+  }
+}
+
+class C {
+  let F: i32 = 0;
+  __mix M;
+}
+
+fn Main() -> i32 {
+  return 0;
+}

+ 29 - 0
explorer/testdata/mixin/fail-method-member-name-clash.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
+// CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/mixin/fail-method-member-name-clash.carbon:25: Member named F (declared at {{.*}}/explorer/testdata/mixin/fail-method-member-name-clash.carbon:17) cannot be mixed into C because it clashes with an existing member with the same name (declared at {{.*}}/explorer/testdata/mixin/fail-method-member-name-clash.carbon:23)
+
+package ExplorerTest api;
+
+__mixin M {
+  fn F[me: Self](x: Self) -> Self{
+     return x;
+  }
+}
+
+class C {
+  fn F[me: Self](x: Self) -> Self {
+     return x;
+  }
+  __mix M;
+}
+
+fn Main() -> i32 {
+  return 0;
+}

+ 19 - 0
explorer/testdata/mixin/fail-mix-as-type-expr.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;
+
+__mixin Operations {}
+
+fn Main() -> i32 {
+// CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/mixin/fail-mix-as-type-expr.carbon:[[@LINE+1]]: Expected a type, but got mixin Operations
+   var a: Operations;
+   return 0;
+}

+ 38 - 0
explorer/testdata/mixin/fail-mix-diamond-clash.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
+// CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/mixin/fail-mix-diamond-clash.carbon:34: Member named F1 (declared at {{.*}}/explorer/testdata/mixin/fail-mix-diamond-clash.carbon:16) is being mixed multiple times into C
+package ExplorerTest api;
+
+__mixin M1 {
+  fn F1[me: Self](x: Self) -> Self{
+     return x;
+  }
+}
+
+__mixin M2 {
+  fn F2() {
+  }
+  __mix M1;
+}
+
+__mixin M3 {
+  __mix M1;
+  fn F3() {
+  }
+}
+
+class C {
+  __mix M2;
+  __mix M3;
+}
+
+fn Main() -> i32 {
+  return 0;
+}

+ 49 - 0
explorer/testdata/mixin/fail-mix-in-global.carbon

@@ -0,0 +1,49 @@
+// 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;
+
+__mixin Operations {
+  fn Square[me: Self](x:i32) -> i32{
+    return x * x;
+  }
+}
+
+// CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/mixin/fail-mix-in-global.carbon:[[@LINE+1]]: syntax error, unexpected MIX, expecting END_OF_FILE
+__mix Operations;
+
+class Point {
+  fn Origin() -> Point {
+    return {.x = 0, .y = 0};
+  }
+
+  var x: i32;
+  var y: i32;
+  __mix Operations;
+}
+
+class Complex {
+  fn Zero() -> Complex {
+    return {.r = 0, .i = 0};
+  }
+
+  var r: i32;
+  var i: i32;
+  __mix Operations;
+}
+
+
+fn Main() -> i32 {
+  var p: Point = Point.Origin();
+  var c: Complex = Complex.Zero();
+  p.x = 3;
+  c.r = 3;
+  return p.Square(p.y) - c.Square(c.i);
+}

+ 29 - 0
explorer/testdata/mixin/fail-mix-in-impl.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;
+
+__mixin Operations {
+  fn Square[me: Self](x:i32) -> i32{
+    return x * x;
+  }
+}
+
+interface A {
+  fn F();
+}
+
+external impl i32 as A {
+  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/mixin/fail-mix-in-impl.carbon:[[@LINE+1]]: syntax error, unexpected MIX, expecting ALIAS or FN or RIGHT_CURLY_BRACE
+  __mix Operations;
+  fn F() {}
+}
+
+fn Main() -> i32 { return 0; }

+ 31 - 0
explorer/testdata/mixin/fail-mix-members-clash.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: %{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
+// CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/mixin/fail-mix-members-clash.carbon:27: Member named F1 (declared at {{.*}}/explorer/testdata/mixin/fail-mix-members-clash.carbon:21) cannot be mixed into C because it clashes with an existing member with the same name (declared at {{.*}}/explorer/testdata/mixin/fail-mix-members-clash.carbon:16)
+package ExplorerTest api;
+
+__mixin M1 {
+  fn F1[me: Self](x: Self) -> Self{
+     return x;
+  }
+}
+
+__mixin M2 {
+  fn F1() {
+  }
+}
+
+class C {
+  __mix M1;
+  __mix M2;
+}
+
+fn Main() -> i32 {
+  return 0;
+}

+ 26 - 0
explorer/testdata/mixin/fail-recursive-mixing.carbon

@@ -0,0 +1,26 @@
+// 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
+// CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/mixin/fail-recursive-mixing.carbon:18: Member named F1 (declared at {{.*}}/explorer/testdata/mixin/fail-recursive-mixing.carbon:16) is being mixed multiple times into M1
+package ExplorerTest api;
+
+__mixin M1 {
+  fn F1[me: Self](x: Self) -> Self{
+     return x;
+  }
+  __mix M1;
+}
+
+class C {
+  __mix M1;
+}
+
+fn Main() -> i32 {
+  return 0;
+}

+ 45 - 0
explorer/testdata/mixin/fail-self-substitution.carbon

@@ -0,0 +1,45 @@
+// 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;
+
+__mixin Operations {
+  fn F[me: Self](x: Self) -> Self{
+     return x;
+  }
+}
+
+class Point {
+  fn Origin() -> Point {
+    return {.x = 0, .y = 0};
+  }
+
+  var x: i32;
+  var y: i32;
+  __mix Operations;
+}
+
+class Complex {
+  fn Zero() -> Complex {
+    return {.r = 0, .i = 0};
+  }
+
+  var r: i32;
+  var i: i32;
+}
+
+fn Main() -> i32 {
+  var p: Point = Point.Origin();
+  var c: Complex = {.r = 42, .i = 1};
+// CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/mixin/fail-self-substitution.carbon:[[@LINE+1]]: type error in call: 'class Complex' is not implicitly convertible to 'class Point'
+  var p1: Point = p.F(c);
+
+  return p1.x - 42;
+}

+ 39 - 0
explorer/testdata/mixin/self-substitution.carbon

@@ -0,0 +1,39 @@
+// 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;
+
+__mixin Operations {
+  // Here Self which is both the input and output type is a type variable
+  fn F[me: Self](x: Self) -> Self {
+     return x;
+  }
+}
+
+class Point {
+  fn Origin() -> Point {
+    return {.x = 0, .y = 0};
+  }
+
+  var x: i32;
+  var y: i32;
+  __mix Operations;
+}
+
+fn Main() -> i32 {
+  var p1: Point = Point.Origin();
+  var p2: Point = {.x = 42, .y = 1};
+  // After accessing the mixin member F through a Point object, the
+  // input and output type variables of F get substituted with
+  // the Point class type
+  var p3: Point = p1.F(p2);
+  return p3.x - 42;
+}

+ 34 - 0
explorer/testdata/mixin/simple-mix-in-mixin.carbon

@@ -0,0 +1,34 @@
+// 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;
+
+__mixin M1 {
+  fn Scale10[me:Self](x:i32) -> i32{
+     return x * 10;
+  }
+}
+
+__mixin M2 {
+  __mix M1;
+  fn Square[me: Self](x:i32) -> i32{
+    return x * x;
+  }
+}
+
+class C {
+  __mix M2;
+}
+
+fn Main() -> i32 {
+  var c: C = {};
+  return c.Square(11) - c.Scale10(10) - 21;
+}

+ 47 - 0
explorer/testdata/mixin/simple-reuse.carbon

@@ -0,0 +1,47 @@
+// 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;
+
+__mixin Operations {
+  fn Square[me: Self](x:i32) -> i32{
+    return x * x;
+  }
+}
+
+class Point {
+  fn Origin() -> Point {
+    return {.x = 0, .y = 0};
+  }
+
+  var x: i32;
+  var y: i32;
+  __mix Operations;
+}
+
+class Complex {
+  fn Zero() -> Complex {
+    return {.r = 0, .i = 0};
+  }
+
+  var r: i32;
+  var i: i32;
+  __mix Operations;
+}
+
+
+fn Main() -> i32 {
+  var p: Point = Point.Origin();
+  var c: Complex = Complex.Zero();
+  p.x = 3;
+  c.r = 4;
+  return p.Square(p.x) - c.Square(c.r) + 7;
+}

+ 44 - 0
explorer/testdata/mixin/use-mixin-method-in-class-method.carbon

@@ -0,0 +1,44 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// RUN: %{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;
+
+__mixin Operations {
+  fn Square[me: Self](x:i32) -> i32{
+    return x * x;
+  }
+}
+
+class Point {
+  var x: i32;
+  var y: i32;
+  fn DistanceSquare[me:Self](other:Self) -> i32 {
+    return me.Square(me.x - other.x) + me.Square(me.y - other.y);
+  }
+  __mix Operations;
+}
+
+class Complex {
+  var r: i32;
+  var i: i32;
+  __mix Operations;
+  fn AbsSquare[me:Self]() -> i32 {
+    return me.Square(me.r) + me.Square(me.i);
+  }
+}
+
+
+fn Main() -> i32 {
+  var p1: Point = {.x = 1, .y = 2 };
+  var p2: Point = {.x = 4, .y = 3 };
+  var c: Complex = {.r = 5, .i = 6 };
+  return c.AbsSquare() - p1.DistanceSquare(p2) - 51;
+}