diff --git a/common/fuzzing/carbon.proto b/common/fuzzing/carbon.proto index 4ca448c869bb4..06431bfc1a417 100644 --- a/common/fuzzing/carbon.proto +++ b/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; } } diff --git a/common/fuzzing/proto_to_carbon.cpp b/common/fuzzing/proto_to_carbon.cpp index 07094700307d8..91635acfd0889 100644 --- a/common/fuzzing/proto_to_carbon.cpp +++ b/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 "; diff --git a/explorer/ast/ast_rtti.txt b/explorer/ast/ast_rtti.txt index aa4d4698e49fb..fe19c8844f9d8 100644 --- a/explorer/ast/ast_rtti.txt +++ b/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; diff --git a/explorer/ast/declaration.cpp b/explorer/ast/declaration.cpp index 5f82e8a5aa1e4..487102131a718 100644 --- a/explorer/ast/declaration.cpp +++ b/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(*this); + PrintID(out); + out << "{\n"; + for (Nonnull m : mixin_decl.members()) { + out << *m; + } + out << "}\n"; + break; + } + case DeclarationKind::MixDeclaration: { + const auto& mix_decl = cast(*this); + PrintID(out); + out << mix_decl.mixin() << ";"; + break; + } case DeclarationKind::ChoiceDeclaration: { const auto& choice = cast(*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(*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(*this); out << "choice " << choice.name(); @@ -161,6 +187,12 @@ auto GetName(const Declaration& declaration) return cast(declaration).name(); case DeclarationKind::ClassDeclaration: return cast(declaration).name(); + case DeclarationKind::MixinDeclaration: { + return cast(declaration).name(); + } + case DeclarationKind::MixDeclaration: { + return std::nullopt; + } case DeclarationKind::ChoiceDeclaration: return cast(declaration).name(); case DeclarationKind::InterfaceDeclaration: diff --git a/explorer/ast/declaration.h b/explorer/ast/declaration.h index 1a1a32f9316b2..b6f5919e1f6c7 100644 --- a/explorer/ast/declaration.h +++ b/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> members_; }; +// EXPERIMENTAL MIXIN FEATURE +class MixinDeclaration : public Declaration { + public: + using ImplementsCarbonValueNode = void; + + MixinDeclaration(SourceLocation source_loc, std::string name, + std::optional> params, + Nonnull self, + std::vector> 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> { + return params_; + } + auto params() -> std::optional> { return params_; } + auto self() const -> Nonnull { return self_; } + auto self() -> Nonnull { return self_; } + auto members() const -> llvm::ArrayRef> { + return members_; + } + + auto value_category() const -> ValueCategory { return ValueCategory::Let; } + + private: + std::string name_; + std::optional> params_; + Nonnull self_; + std::vector> members_; +}; + +// EXPERIMENTAL MIXIN FEATURE +class MixDeclaration : public Declaration { + public: + MixDeclaration(SourceLocation source_loc, + std::optional> 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 mixin_value) { + mixin_value_ = mixin_value; + } + + private: + std::optional> mixin_; + Nonnull mixin_value_; +}; + class AlternativeSignature : public AstNode { public: AlternativeSignature(SourceLocation source_loc, std::string name, diff --git a/explorer/fuzzing/ast_to_proto.cpp b/explorer/fuzzing/ast_to_proto.cpp index b93d0f89e59f5..3dc4c28ca8c49 100644 --- a/explorer/fuzzing/ast_to_proto.cpp +++ b/explorer/fuzzing/ast_to_proto.cpp @@ -607,6 +607,29 @@ static auto DeclarationToProto(const Declaration& declaration) break; } + case DeclarationKind::MixinDeclaration: { + const auto& mixin = cast(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(declaration); + auto* mix_proto = declaration_proto.mutable_mix(); + *mix_proto->mutable_mixin() = ExpressionToProto(mix.mixin()); + break; + } + case DeclarationKind::ChoiceDeclaration: { const auto& choice = cast(declaration); auto* choice_proto = declaration_proto.mutable_choice(); diff --git a/explorer/interpreter/interpreter.cpp b/explorer/interpreter/interpreter.cpp index 2c646ecdfbf84..0401be44c3c99 100644 --- a/explorer/interpreter/interpreter.cpp +++ b/explorer/interpreter/interpreter.cpp @@ -625,6 +625,7 @@ auto Interpreter::Convert(Nonnull 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 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 { } case DeclarationKind::FunctionDeclaration: case DeclarationKind::ClassDeclaration: + case DeclarationKind::MixinDeclaration: + case DeclarationKind::MixDeclaration: case DeclarationKind::ChoiceDeclaration: case DeclarationKind::InterfaceDeclaration: case DeclarationKind::AssociatedConstantDeclaration: diff --git a/explorer/interpreter/resolve_control_flow.cpp b/explorer/interpreter/resolve_control_flow.cpp index b96d38c4d33a7..0d5cc786cf8b4 100644 --- a/explorer/interpreter/resolve_control_flow.cpp +++ b/explorer/interpreter/resolve_control_flow.cpp @@ -154,6 +154,13 @@ auto ResolveControlFlow(Nonnull declaration) -> ErrorOr { } break; } + case DeclarationKind::MixinDeclaration: { + auto& mixin_decl = cast(*declaration); + for (Nonnull member : mixin_decl.members()) { + CARBON_RETURN_IF_ERROR(ResolveControlFlow(member)); + } + break; + } case DeclarationKind::InterfaceDeclaration: { auto& iface_decl = cast(*declaration); for (Nonnull member : iface_decl.members()) { @@ -173,6 +180,7 @@ auto ResolveControlFlow(Nonnull declaration) -> ErrorOr { case DeclarationKind::AssociatedConstantDeclaration: case DeclarationKind::SelfDeclaration: case DeclarationKind::AliasDeclaration: + case DeclarationKind::MixDeclaration: // do nothing break; } diff --git a/explorer/interpreter/resolve_names.cpp b/explorer/interpreter/resolve_names.cpp index 00a1f47d5bec4..aacc8358b3852 100644 --- a/explorer/interpreter/resolve_names.cpp +++ b/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(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(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(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(declaration); + CARBON_RETURN_IF_ERROR(ResolveNames(mix_decl.mixin(), enclosing_scope)); + break; + } case DeclarationKind::ChoiceDeclaration: { auto& choice = cast(declaration); StaticScope choice_scope; diff --git a/explorer/interpreter/resolve_unformed.cpp b/explorer/interpreter/resolve_unformed.cpp index 83e23184986e7..d34d9c3008886 100644 --- a/explorer/interpreter/resolve_unformed.cpp +++ b/explorer/interpreter/resolve_unformed.cpp @@ -294,6 +294,8 @@ static auto ResolveUnformed(Nonnull declaration) break; } case DeclarationKind::ClassDeclaration: + case DeclarationKind::MixDeclaration: + case DeclarationKind::MixinDeclaration: case DeclarationKind::InterfaceDeclaration: case DeclarationKind::ImplDeclaration: case DeclarationKind::ChoiceDeclaration: diff --git a/explorer/interpreter/type_checker.cpp b/explorer/interpreter/type_checker.cpp index 3416eb406ad6d..001e14f5a4bb0 100644 --- a/explorer/interpreter/type_checker.cpp +++ b/explorer/interpreter/type_checker.cpp @@ -194,6 +194,7 @@ static auto IsTypeOfType(Nonnull 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 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 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 e, } case Value::Kind::NominalClassType: { const auto& t_class = cast(object_type); - if (std::optional> 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 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(*member); + auto func_decl = cast(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 e, case Value::Kind::NominalClassType: { const NominalClassType& class_type = cast(*type); - if (std::optional> 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(*member); - if (func->is_method()) { + if (func.is_method()) { break; } Nonnull 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 e, break; } access.set_static_type( - arena_->New(Member(*member))); + arena_->New(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 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 mixin_decl, + const ScopeInfo& scope_info) + -> ErrorOr { + 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 param_name = + arena_->New(mixin_decl, *mixin_decl->params()); + SetConstantValue(mixin_decl, param_name); + mixin_decl->set_static_type( + arena_->New(param_name)); + } else { + Nonnull mixin_type = + arena_->New(mixin_decl); + SetConstantValue(mixin_decl, mixin_type); + mixin_decl->set_static_type(arena_->New(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 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 mixin_decl, const ImplScope& impl_scope) + -> ErrorOr { + 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 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 mix_decl, const ImplScope& impl_scope, + std::optional> enclosing_decl) + -> ErrorOr { + 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 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 iface_decl, const ScopeInfo& scope_info) -> ErrorOr { @@ -3521,7 +3667,7 @@ auto TypeChecker::TypeCheckInterfaceDeclaration( **trace_stream_ << iface_scope; } for (Nonnull 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 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 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 { DeclareDeclaration(declaration, top_level_scope_info)); } for (Nonnull 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 { return Success(); } -auto TypeChecker::TypeCheckDeclaration(Nonnull d, - const ImplScope& impl_scope) +auto TypeChecker::TypeCheckDeclaration( + Nonnull d, const ImplScope& impl_scope, + std::optional> enclosing_decl) -> ErrorOr { if (trace_stream_) { **trace_stream_ << "checking " << DeclarationKindName(d->kind()) << "\n"; @@ -3989,6 +4139,16 @@ auto TypeChecker::TypeCheckDeclaration(Nonnull d, CARBON_RETURN_IF_ERROR( TypeCheckClassDeclaration(&cast(*d), impl_scope)); return Success(); + case DeclarationKind::MixinDeclaration: { + CARBON_RETURN_IF_ERROR( + TypeCheckMixinDeclaration(&cast(*d), impl_scope)); + return Success(); + } + case DeclarationKind::MixDeclaration: { + CARBON_RETURN_IF_ERROR(TypeCheckMixDeclaration( + &cast(*d), impl_scope, enclosing_decl)); + return Success(); + } case DeclarationKind::ChoiceDeclaration: CARBON_RETURN_IF_ERROR( TypeCheckChoiceDeclaration(&cast(*d), impl_scope)); @@ -4052,7 +4212,19 @@ auto TypeChecker::DeclareDeclaration(Nonnull d, CARBON_RETURN_IF_ERROR(DeclareClassDeclaration(&class_decl, scope_info)); break; } - + case DeclarationKind::MixinDeclaration: { + auto& mixin_decl = cast(*d); + CARBON_RETURN_IF_ERROR(DeclareMixinDeclaration(&mixin_decl, scope_info)); + break; + } + case DeclarationKind::MixDeclaration: { + auto& mix_decl = cast(*d); + CARBON_ASSIGN_OR_RETURN( + Nonnull mixin, + InterpExp(&mix_decl.mixin(), arena_, trace_stream_)); + mix_decl.set_mixin_value(cast(mixin)); + break; + } case DeclarationKind::ChoiceDeclaration: { auto& choice = cast(*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> members, + const Nonnull enclosing_type) + -> std::optional< + std::pair, Nonnull>> { + for (Nonnull member : members) { + if (llvm::isa(member)) { + const auto& mix_decl = cast(*member); + Nonnull mixin = &mix_decl.mixin_value(); + const auto res = + FindMixedMemberAndType(name, mixin->declaration().members(), mixin); + if (res.has_value()) { + if (isa(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 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 enclosing_decl, + Nonnull member_decl) + -> ErrorOr { + CARBON_CHECK(isa(enclosing_decl) || + isa(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 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 diff --git a/explorer/interpreter/type_checker.h b/explorer/interpreter/type_checker.h index c1530340da194..7606746d5c20d 100644 --- a/explorer/interpreter/type_checker.h +++ b/explorer/interpreter/type_checker.h @@ -19,6 +19,12 @@ namespace Carbon { +using CollectedMembersMap = + std::unordered_map>; + +using GlobalMembersMap = + std::unordered_map, CollectedMembersMap>; + class TypeChecker { public: explicit TypeChecker(Nonnull arena, @@ -64,6 +70,20 @@ class TypeChecker { SourceLocation source_loc) const -> std::optional>; + /* + ** 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> members, + const Nonnull enclosing_type) + -> std::optional< + std::pair, Nonnull>>; + // 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 class_decl, const ScopeInfo& scope_info) -> ErrorOr; + auto DeclareMixinDeclaration(Nonnull mixin_decl, + const ScopeInfo& scope_info) -> ErrorOr; + auto DeclareInterfaceDeclaration(Nonnull iface_decl, const ScopeInfo& scope_info) -> ErrorOr; @@ -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 d, - const ImplScope& impl_scope) -> ErrorOr; + auto TypeCheckDeclaration( + Nonnull d, const ImplScope& impl_scope, + std::optional> enclosing_decl) + -> ErrorOr; // Type check the body of the function. auto TypeCheckFunctionDeclaration(Nonnull f, @@ -259,6 +284,16 @@ class TypeChecker { const ImplScope& impl_scope) -> ErrorOr; + // Type check all the members of the mixin. + auto TypeCheckMixinDeclaration(Nonnull mixin_decl, + const ImplScope& impl_scope) + -> ErrorOr; + + auto TypeCheckMixDeclaration( + Nonnull mix_decl, const ImplScope& impl_scope, + std::optional> enclosing_decl) + -> ErrorOr; + // Type check all the members of the interface. auto TypeCheckInterfaceDeclaration(Nonnull 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 enclosing_decl, + Nonnull member_decl) + -> ErrorOr; + + /* + ** Fetches all direct and indirect members of a class or mixin declaration + ** stored within collected_members_ + */ + auto FindCollectedMembers(Nonnull decl) + -> CollectedMembersMap&; + Nonnull arena_; std::set constants_; Builtins builtins_; + // Maps a mixin/class declaration to all of its direct and indirect members. + GlobalMembersMap collected_members_; + std::optional> trace_stream_; }; diff --git a/explorer/interpreter/value.cpp b/explorer/interpreter/value.cpp index 398f0631322d8..2a2b7f299e147 100644 --- a/explorer/interpreter/value.cpp +++ b/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(*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(*this); out << "interface "; @@ -491,6 +505,14 @@ void Value::Print(llvm::raw_ostream& out) const { case Value::Kind::TypeOfClassType: out << "typeof(" << cast(*this).class_type() << ")"; break; + case Value::Kind::TypeOfMixinPseudoType: + out << "typeof(" + << cast(*this) + .mixin_type() + .declaration() + .name() + << ")"; + break; case Value::Kind::TypeOfInterfaceType: out << "typeof(" << cast(*this) @@ -731,6 +753,8 @@ auto TypeEqual(Nonnull t1, Nonnull 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> { for (const auto& member : declaration().members()) { switch (member->kind()) { + case DeclarationKind::MixDeclaration: { + const auto& mix_decl = cast(*member); + Nonnull 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(*member); + if (fun.name() == name) { + return &cast(**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> { + for (const auto& member : declaration().members()) { + switch (member->kind()) { + case DeclarationKind::MixDeclaration: { + const auto& mix_decl = cast(*member); + Nonnull 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(*member); if (fun.name() == name) { diff --git a/explorer/interpreter/value.h b/explorer/interpreter/value.h index 5d221f07bd49b..2058f40df57c7 100644 --- a/explorer/interpreter/value.h +++ b/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 bindings_ = Bindings::None(); }; +class MixinPseudoType : public Value { + public: + explicit MixinPseudoType(Nonnull declaration) + : Value(Kind::MixinPseudoType), declaration_(declaration) { + CARBON_CHECK(!declaration->params().has_value()) + << "missing arguments for parameterized mixin type"; + } + explicit MixinPseudoType(Nonnull declaration, + Nonnull 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>; + + private: + Nonnull declaration_; + Nonnull bindings_ = Bindings::None(); +}; + // Return the declaration of the member with the given name. auto FindMember(std::string_view name, llvm::ArrayRef> members) @@ -1097,6 +1134,21 @@ class TypeOfClassType : public Value { Nonnull class_type_; }; +class TypeOfMixinPseudoType : public Value { + public: + explicit TypeOfMixinPseudoType(Nonnull 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 mixin_type_; +}; + class TypeOfInterfaceType : public Value { public: explicit TypeOfInterfaceType(Nonnull iface_type) diff --git a/explorer/syntax/lexer.lpp b/explorer/syntax/lexer.lpp index 6af5bfb4bf37e..241ed780d465a 100644 --- a/explorer/syntax/lexer.lpp +++ b/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); } diff --git a/explorer/syntax/parser.ypp b/explorer/syntax/parser.ypp index 638d5297ff936..c7abc6d7f8cc0 100644 --- a/explorer/syntax/parser.ypp +++ b/explorer/syntax/parser.ypp @@ -108,8 +108,11 @@ %type >> class_declaration_extends %type > declaration %type > function_declaration +%type > mix_declaration %type > alias_declaration %type >> declaration_list +%type >> class_body +%type >> mixin_body %type >> interface_body %type >> impl_body %type > statement @@ -160,6 +163,7 @@ %type > statement_expression %type > if_expression %type > expression +%type > mixin_import %type > generic_binding %type > deduced_param %type >> 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(context.source_loc(), $2, $4); } ; +// EXPERIMENTAL MIXIN FEATURE +mix_declaration: MIX expression SEMICOLON + { $$ = arena->New(context.source_loc(), $2); } +; alternative: identifier tuple { $$ = arena->New(context.source_loc(), $1, $2); } @@ -1099,6 +1109,17 @@ type_params: | tuple_pattern { $$ = $1; } ; +// EXPERIMENTAL MIXIN FEATURE +mixin_import: + // Empty + { $$ = arena->New(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( context.source_loc(), $3, arena->New(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(context.source_loc(), "Self", $4); + $$ = arena->New(context.source_loc(), $2, $3, self, $6); + } | CHOICE identifier type_params LEFT_CURLY_BRACE alternative_list RIGHT_CURLY_BRACE { $$ = arena->New(context.source_loc(), $2, $3, $5); } | VAR variable_declaration SEMICOLON @@ -1184,6 +1212,35 @@ declaration_list: $$.push_back(Nonnull($2)); } ; +class_body: + // Empty + { $$ = {}; } +| class_body declaration + { + $$ = $1; + $$.push_back(Nonnull($2)); + } +| class_body mix_declaration + { + $$ = $1; + $$.push_back(Nonnull($2)); + } +; +// EXPERIMENTAL MIXIN FEATURE +mixin_body: + // Empty + { $$ = {}; } +| mixin_body function_declaration + { + $$ = $1; + $$.push_back(Nonnull($2)); + } +| mixin_body mix_declaration + { + $$ = $1; + $$.push_back(Nonnull($2)); + } +; interface_body: // Empty { $$ = {}; } diff --git a/explorer/testdata/mixin/fail-circular-mixing.carbon b/explorer/testdata/mixin/fail-circular-mixing.carbon new file mode 100644 index 0000000000000..9f504f9ca2890 --- /dev/null +++ b/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; +} diff --git a/explorer/testdata/mixin/fail-field-member-name-clash.carbon b/explorer/testdata/mixin/fail-field-member-name-clash.carbon new file mode 100644 index 0000000000000..d98d547ef9af5 --- /dev/null +++ b/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; +} diff --git a/explorer/testdata/mixin/fail-method-member-name-clash.carbon b/explorer/testdata/mixin/fail-method-member-name-clash.carbon new file mode 100644 index 0000000000000..e9e4854615d12 --- /dev/null +++ b/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; +} diff --git a/explorer/testdata/mixin/fail-mix-as-type-expr.carbon b/explorer/testdata/mixin/fail-mix-as-type-expr.carbon new file mode 100644 index 0000000000000..b6b13ba11527a --- /dev/null +++ b/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; +} diff --git a/explorer/testdata/mixin/fail-mix-diamond-clash.carbon b/explorer/testdata/mixin/fail-mix-diamond-clash.carbon new file mode 100644 index 0000000000000..7f5d629662058 --- /dev/null +++ b/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; +} diff --git a/explorer/testdata/mixin/fail-mix-in-global.carbon b/explorer/testdata/mixin/fail-mix-in-global.carbon new file mode 100644 index 0000000000000..21ab9ac4bd32e --- /dev/null +++ b/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); +} diff --git a/explorer/testdata/mixin/fail-mix-in-impl.carbon b/explorer/testdata/mixin/fail-mix-in-impl.carbon new file mode 100644 index 0000000000000..cfa561db6f1b8 --- /dev/null +++ b/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; } diff --git a/explorer/testdata/mixin/fail-mix-members-clash.carbon b/explorer/testdata/mixin/fail-mix-members-clash.carbon new file mode 100644 index 0000000000000..8cddf82cd1a12 --- /dev/null +++ b/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; +} diff --git a/explorer/testdata/mixin/fail-recursive-mixing.carbon b/explorer/testdata/mixin/fail-recursive-mixing.carbon new file mode 100644 index 0000000000000..ec7fe5f44063b --- /dev/null +++ b/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; +} diff --git a/explorer/testdata/mixin/fail-self-substitution.carbon b/explorer/testdata/mixin/fail-self-substitution.carbon new file mode 100644 index 0000000000000..049f96d17250c --- /dev/null +++ b/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; +} diff --git a/explorer/testdata/mixin/self-substitution.carbon b/explorer/testdata/mixin/self-substitution.carbon new file mode 100644 index 0000000000000..d66f78f19d656 --- /dev/null +++ b/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; +} diff --git a/explorer/testdata/mixin/simple-mix-in-mixin.carbon b/explorer/testdata/mixin/simple-mix-in-mixin.carbon new file mode 100644 index 0000000000000..0e0012e668bbe --- /dev/null +++ b/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; +} diff --git a/explorer/testdata/mixin/simple-reuse.carbon b/explorer/testdata/mixin/simple-reuse.carbon new file mode 100644 index 0000000000000..855e1310f0544 --- /dev/null +++ b/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; +} diff --git a/explorer/testdata/mixin/use-mixin-method-in-class-method.carbon b/explorer/testdata/mixin/use-mixin-method-in-class-method.carbon new file mode 100644 index 0000000000000..bd0b5687b5ee9 --- /dev/null +++ b/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; +}