diff --git a/common/fuzzing/carbon.proto b/common/fuzzing/carbon.proto index 280ca80fc406..9bbb749fd4b1 100644 --- a/common/fuzzing/carbon.proto +++ b/common/fuzzing/carbon.proto @@ -390,7 +390,11 @@ message InterfaceImplDeclaration { message InterfaceDeclaration { optional string name = 1; repeated Declaration members = 2; - optional GenericBinding self = 3; +} + +message ConstraintDeclaration { + optional string name = 1; + repeated Declaration members = 2; } message ImplDeclaration { @@ -440,6 +444,7 @@ message Declaration { DestructorDeclaration destructor = 11; InterfaceExtendsDeclaration interface_extends = 12; InterfaceImplDeclaration interface_impl = 13; + ConstraintDeclaration constraint = 14; } } diff --git a/common/fuzzing/proto_to_carbon.cpp b/common/fuzzing/proto_to_carbon.cpp index 99c53ab6b740..fa09477362d9 100644 --- a/common/fuzzing/proto_to_carbon.cpp +++ b/common/fuzzing/proto_to_carbon.cpp @@ -855,7 +855,19 @@ static auto DeclarationToCarbon(const Fuzzing::Declaration& declaration, out << "\n"; } out << "}"; - // TODO: need to handle interface.self()? + break; + } + + case Fuzzing::Declaration::kConstraint: { + const auto& constraint = declaration.constraint(); + out << "constraint "; + IdentifierToCarbon(constraint.name(), out); + out << " {\n"; + for (const auto& member : constraint.members()) { + DeclarationToCarbon(member, out); + out << "\n"; + } + out << "}"; break; } diff --git a/explorer/ast/ast_rtti.txt b/explorer/ast/ast_rtti.txt index 70c0c50640dc..26f250f6eeae 100644 --- a/explorer/ast/ast_rtti.txt +++ b/explorer/ast/ast_rtti.txt @@ -22,7 +22,9 @@ abstract class Declaration : AstNode; class MixDeclaration : Declaration; class ChoiceDeclaration : Declaration; class VariableDeclaration : Declaration; - class InterfaceDeclaration : Declaration; + abstract class ConstraintTypeDeclaration : Declaration; + class InterfaceDeclaration : ConstraintTypeDeclaration; + class ConstraintDeclaration : ConstraintTypeDeclaration; class InterfaceExtendsDeclaration : Declaration; class InterfaceImplDeclaration : Declaration; class AssociatedConstantDeclaration : Declaration; diff --git a/explorer/ast/declaration.cpp b/explorer/ast/declaration.cpp index 8e02ff5a113f..1b84ce9ad077 100644 --- a/explorer/ast/declaration.cpp +++ b/explorer/ast/declaration.cpp @@ -15,8 +15,9 @@ Declaration::~Declaration() = default; void Declaration::Print(llvm::raw_ostream& out) const { switch (kind()) { - case DeclarationKind::InterfaceDeclaration: { - const auto& iface_decl = cast(*this); + case DeclarationKind::InterfaceDeclaration: + case DeclarationKind::ConstraintDeclaration: { + const auto& iface_decl = cast(*this); PrintID(out); out << " {\n"; for (Nonnull m : iface_decl.members()) { @@ -120,6 +121,11 @@ void Declaration::PrintID(llvm::raw_ostream& out) const { out << "interface " << iface_decl.name(); break; } + case DeclarationKind::ConstraintDeclaration: { + const auto& constraint_decl = cast(*this); + out << "constraint " << constraint_decl.name(); + break; + } case DeclarationKind::ImplDeclaration: { const auto& impl_decl = cast(*this); switch (impl_decl.kind()) { @@ -217,7 +223,8 @@ auto GetName(const Declaration& declaration) case DeclarationKind::ChoiceDeclaration: return cast(declaration).name(); case DeclarationKind::InterfaceDeclaration: - return cast(declaration).name(); + case DeclarationKind::ConstraintDeclaration: + return cast(declaration).name(); case DeclarationKind::VariableDeclaration: return cast(declaration).binding().name(); case DeclarationKind::AssociatedConstantDeclaration: diff --git a/explorer/ast/declaration.h b/explorer/ast/declaration.h index cee411c9f406..e5522257b924 100644 --- a/explorer/ast/declaration.h +++ b/explorer/ast/declaration.h @@ -487,15 +487,18 @@ class VariableDeclaration : public Declaration { ValueCategory value_category_; }; -class InterfaceDeclaration : public Declaration { +// Base class for constraint and interface declarations. Interfaces and named +// constraints behave the same in most respects, but only interfaces can +// introduce new associated functions and constants. +class ConstraintTypeDeclaration : public Declaration { public: using ImplementsCarbonValueNode = void; - InterfaceDeclaration(Nonnull arena, SourceLocation source_loc, - std::string name, - std::optional> params, - std::vector> members) - : Declaration(AstNodeKind::InterfaceDeclaration, source_loc), + ConstraintTypeDeclaration(AstNodeKind kind, Nonnull arena, + SourceLocation source_loc, std::string name, + std::optional> params, + std::vector> members) + : Declaration(kind, source_loc), name_(std::move(name)), params_(params), self_type_(arena->New(source_loc)), @@ -507,7 +510,7 @@ class InterfaceDeclaration : public Declaration { } static auto classof(const AstNode* node) -> bool { - return InheritsFromInterfaceDeclaration(node->kind()); + return InheritsFromConstraintTypeDeclaration(node->kind()); } auto name() const -> const std::string& { return name_; } @@ -553,6 +556,42 @@ class InterfaceDeclaration : public Declaration { std::optional> constraint_type_; }; +// A `interface` declaration. +class InterfaceDeclaration : public ConstraintTypeDeclaration { + public: + using ImplementsCarbonValueNode = void; + + InterfaceDeclaration(Nonnull arena, SourceLocation source_loc, + std::string name, + std::optional> params, + std::vector> members) + : ConstraintTypeDeclaration(AstNodeKind::InterfaceDeclaration, arena, + source_loc, std::move(name), params, + std::move(members)) {} + + static auto classof(const AstNode* node) -> bool { + return InheritsFromInterfaceDeclaration(node->kind()); + } +}; + +// A `constraint` declaration, such as `constraint X { impl as Y; }`. +class ConstraintDeclaration : public ConstraintTypeDeclaration { + public: + using ImplementsCarbonValueNode = void; + + ConstraintDeclaration(Nonnull arena, SourceLocation source_loc, + std::string name, + std::optional> params, + std::vector> members) + : ConstraintTypeDeclaration(AstNodeKind::ConstraintDeclaration, arena, + source_loc, std::move(name), params, + std::move(members)) {} + + static auto classof(const AstNode* node) -> bool { + return InheritsFromConstraintDeclaration(node->kind()); + } +}; + // An `extends` declaration in an interface. class InterfaceExtendsDeclaration : public Declaration { public: diff --git a/explorer/data/prelude.carbon b/explorer/data/prelude.carbon index 492fd22e2924..382acd97eff0 100644 --- a/explorer/data/prelude.carbon +++ b/explorer/data/prelude.carbon @@ -18,7 +18,7 @@ interface ImplicitAs(T:! Type) { extends As(T); } -// TODO: Should we just use an intrinsic for this? +// TODO: This should be private. interface __EqualConverter { let T:! Type; fn Convert(t: T) -> Self; @@ -72,8 +72,10 @@ interface EqWith(U:! Type) { fn Equal[me: Self](other: U) -> bool; fn NotEqual[me: Self](other: U) -> bool; } -// TODO: constraint Eq { ... } +constraint Eq { + extends EqWith(Self); +} // TODO: Simplify this once we have variadics impl forall [T2:! Type, U2:! Type, T1:! EqWith(T2), U1:! EqWith(U2)] @@ -130,11 +132,14 @@ choice Ordering { Incomparable } +// TODO: Per the design, this should be named `OrderedWith`. interface CompareWith(U:! Type) { fn Compare[me: Self](u: U) -> Ordering; // TODO: Add `default fn` for Less, LessOrEquivalent, Greater, and GreaterOrEquivalent once it's available. } -// TODO: constraint Ordered { ... } +constraint Ordered { + extends CompareWith(Self); +} impl i32 as CompareWith(Self) { fn Compare[me: Self](other: Self) -> Ordering { @@ -308,35 +313,45 @@ interface AddWith(U:! Type) { let Result:! Type; fn Op[me: Self](other: U) -> Result; } -// TODO: constraint Add { ... } +constraint Add { + extends AddWith(Self) where .Result = Self; +} interface SubWith(U:! Type) { // TODO: = Self let Result:! Type; fn Op[me: Self](other: U) -> Result; } -// TODO: constraint Sub { ... } +constraint Sub { + extends SubWith(Self) where .Result = Self; +} interface MulWith(U:! Type) { // TODO: = Self let Result:! Type; fn Op[me: Self](other: U) -> Result; } -// TODO: constraint Mul { ... } +constraint Mul { + extends MulWith(Self) where .Result = Self; +} interface DivWith(U:! Type) { // TODO: = Self let Result:! Type; fn Op[me: Self](other: U) -> Result; } -// TODO: constraint Div { ... } +constraint Div { + extends DivWith(Self) where .Result = Self; +} interface ModWith(U:! Type) { // TODO: = Self let Result:! Type; fn Op[me: Self](other: U) -> Result; } -// TODO: constraint Mod { ... } +constraint Mod { + extends ModWith(Self) where .Result = Self; +} // Note, these impls use the builtin addition for i32. external impl i32 as Negate where .Result = i32 { @@ -375,10 +390,9 @@ interface BitAndWith(U:! Type) { let Result:! Type; fn Op[me: Self](other: U) -> Result; } -// TODO: -// constraint BitAnd { -// extends BitAndWith(Self) where .Result = Self; -// } +constraint BitAnd { + extends BitAndWith(Self) where .Result = Self; +} // Binary `|`. interface BitOrWith(U:! Type) { @@ -386,10 +400,9 @@ interface BitOrWith(U:! Type) { let Result:! Type; fn Op[me: Self](other: U) -> Result; } -// TODO: -// constraint BitOr { -// extends BitOrWith(Self) where .Result = Self; -// } +constraint BitOr { + extends BitOrWith(Self) where .Result = Self; +} // Binary `^`. interface BitXorWith(U:! Type) { @@ -397,10 +410,9 @@ interface BitXorWith(U:! Type) { let Result:! Type; fn Op[me: Self](other: U) -> Result; } -// TODO: -// constraint BitXor { -// extends BitXorWith(Self) where .Result = Self; -// } +constraint BitXor { + extends BitXorWith(Self) where .Result = Self; +} // Binary `<<`. interface LeftShiftWith(U:! Type) { @@ -408,10 +420,9 @@ interface LeftShiftWith(U:! Type) { let Result:! Type; fn Op[me: Self](other: U) -> Result; } -// TODO: -// constraint LeftShift { -// extends LeftShiftWith(Self) where .Result = Self; -// } +constraint LeftShift { + extends LeftShiftWith(Self) where .Result = Self; +} // Binary `>>`. interface RightShiftWith(U:! Type) { @@ -419,10 +430,9 @@ interface RightShiftWith(U:! Type) { let Result:! Type; fn Op[me: Self](other: U) -> Result; } -// TODO: -// constraint RightShift { -// extends RightShiftWith(Self) where .Result = Self; -// } +constraint RightShift { + extends RightShiftWith(Self) where .Result = Self; +} external impl i32 as BitComplement where .Result = i32 { fn Op[me: i32]() -> i32 { diff --git a/explorer/fuzzing/ast_to_proto.cpp b/explorer/fuzzing/ast_to_proto.cpp index 5a92e7b38919..13089939cc83 100644 --- a/explorer/fuzzing/ast_to_proto.cpp +++ b/explorer/fuzzing/ast_to_proto.cpp @@ -739,8 +739,16 @@ static auto DeclarationToProto(const Declaration& declaration) for (const auto& member : interface.members()) { *interface_proto->add_members() = DeclarationToProto(*member); } - *interface_proto->mutable_self() = - GenericBindingToProto(*interface.self()); + break; + } + + case DeclarationKind::ConstraintDeclaration: { + const auto& constraint = cast(declaration); + auto* constraint_proto = declaration_proto.mutable_constraint(); + constraint_proto->set_name(constraint.name()); + for (const auto& member : constraint.members()) { + *constraint_proto->add_members() = DeclarationToProto(*member); + } break; } diff --git a/explorer/interpreter/interpreter.cpp b/explorer/interpreter/interpreter.cpp index 256ccd7706c5..b653824cd714 100644 --- a/explorer/interpreter/interpreter.cpp +++ b/explorer/interpreter/interpreter.cpp @@ -602,6 +602,14 @@ auto Interpreter::InstantiateType(Nonnull type, return arena_->New(&interface_type.declaration(), bindings); } + case Value::Kind::NamedConstraintType: { + const auto& constraint_type = cast(*type); + CARBON_ASSIGN_OR_RETURN( + Nonnull bindings, + InstantiateBindings(&constraint_type.bindings(), source_loc)); + return arena_->New(&constraint_type.declaration(), + bindings); + } case Value::Kind::NominalClassType: { const auto& class_type = cast(*type); CARBON_ASSIGN_OR_RETURN( @@ -681,6 +689,7 @@ auto Interpreter::Convert(Nonnull value, case Value::Kind::NominalClassType: case Value::Kind::MixinPseudoType: case Value::Kind::InterfaceType: + case Value::Kind::NamedConstraintType: case Value::Kind::ConstraintType: case Value::Kind::ImplWitness: case Value::Kind::BindingWitness: @@ -732,6 +741,7 @@ auto Interpreter::Convert(Nonnull value, } case Value::Kind::TypeType: case Value::Kind::ConstraintType: + case Value::Kind::NamedConstraintType: case Value::Kind::InterfaceType: { CARBON_CHECK(struct_val.elements().empty()) << "only empty structs convert to Type"; @@ -762,6 +772,7 @@ auto Interpreter::Convert(Nonnull value, } case Value::Kind::TypeType: case Value::Kind::ConstraintType: + case Value::Kind::NamedConstraintType: case Value::Kind::InterfaceType: { std::vector> new_elements; Nonnull type_type = arena_->New(); @@ -798,8 +809,9 @@ auto Interpreter::Convert(Nonnull value, EvalAssociatedConstant(cast(value), source_loc)); if (auto* new_const = dyn_cast(value)) { // TODO: Detect whether conversions are required in type-checking. - if (isa(destination_type) && - isa( + if (isa( + destination_type) && + isa( new_const->constant().static_type())) { // No further conversions are required. return value; @@ -958,6 +970,9 @@ auto Interpreter::CallFunction(const CallExpression& call, case DeclarationKind::InterfaceDeclaration: return todo_.FinishAction(arena_->New( &cast(decl), bindings)); + case DeclarationKind::ConstraintDeclaration: + return todo_.FinishAction(arena_->New( + &cast(decl), bindings)); case DeclarationKind::ChoiceDeclaration: return todo_.FinishAction(arena_->New( &cast(decl), bindings)); @@ -1063,7 +1078,8 @@ auto Interpreter::StepExp() -> ErrorOr { CARBON_CHECK(phase() == Phase::CompileTime) << "should not form MemberNames at runtime"; std::optional type_result; - if (!isa(act.results()[0])) { + if (!isa( + act.results()[0])) { type_result = act.results()[0]; } MemberName* member_name = arena_->New( @@ -2001,6 +2017,7 @@ auto Interpreter::StepDeclaration() -> ErrorOr { case DeclarationKind::MixDeclaration: case DeclarationKind::ChoiceDeclaration: case DeclarationKind::InterfaceDeclaration: + case DeclarationKind::ConstraintDeclaration: case DeclarationKind::InterfaceExtendsDeclaration: case DeclarationKind::InterfaceImplDeclaration: case DeclarationKind::AssociatedConstantDeclaration: diff --git a/explorer/interpreter/resolve_control_flow.cpp b/explorer/interpreter/resolve_control_flow.cpp index 6e9098330264..4ea98df091ba 100644 --- a/explorer/interpreter/resolve_control_flow.cpp +++ b/explorer/interpreter/resolve_control_flow.cpp @@ -163,8 +163,9 @@ auto ResolveControlFlow(Nonnull declaration) -> ErrorOr { } break; } - case DeclarationKind::InterfaceDeclaration: { - auto& iface_decl = cast(*declaration); + case DeclarationKind::InterfaceDeclaration: + case DeclarationKind::ConstraintDeclaration: { + auto& iface_decl = cast(*declaration); for (Nonnull member : iface_decl.members()) { CARBON_RETURN_IF_ERROR(ResolveControlFlow(member)); } diff --git a/explorer/interpreter/resolve_names.cpp b/explorer/interpreter/resolve_names.cpp index c8eebbb8d0ef..8e4c8724750d 100644 --- a/explorer/interpreter/resolve_names.cpp +++ b/explorer/interpreter/resolve_names.cpp @@ -22,8 +22,9 @@ namespace Carbon { static auto AddExposedNames(const Declaration& declaration, StaticScope& enclosing_scope) -> ErrorOr { switch (declaration.kind()) { - case DeclarationKind::InterfaceDeclaration: { - const auto& iface_decl = cast(declaration); + case DeclarationKind::InterfaceDeclaration: + case DeclarationKind::ConstraintDeclaration: { + const auto& iface_decl = cast(declaration); CARBON_RETURN_IF_ERROR( enclosing_scope.Add(iface_decl.name(), &iface_decl, StaticScope::NameStatus::KnownButNotDeclared)); @@ -517,8 +518,9 @@ static auto ResolveMemberNames(llvm::ArrayRef> members, static auto ResolveNames(Declaration& declaration, StaticScope& enclosing_scope, ResolveFunctionBodies bodies) -> ErrorOr { switch (declaration.kind()) { - case DeclarationKind::InterfaceDeclaration: { - auto& iface = cast(declaration); + case DeclarationKind::InterfaceDeclaration: + case DeclarationKind::ConstraintDeclaration: { + auto& iface = cast(declaration); StaticScope iface_scope; iface_scope.AddParent(&enclosing_scope); enclosing_scope.MarkDeclared(iface.name()); @@ -527,7 +529,7 @@ static auto ResolveNames(Declaration& declaration, StaticScope& enclosing_scope, } enclosing_scope.MarkUsable(iface.name()); // Don't resolve names in the type of the self binding. The - // InterfaceDeclaration constructor already did that. + // ConstraintTypeDeclaration constructor already did that. CARBON_RETURN_IF_ERROR(iface_scope.Add("Self", iface.self())); CARBON_RETURN_IF_ERROR( ResolveMemberNames(iface.members(), iface_scope, bodies)); diff --git a/explorer/interpreter/resolve_unformed.cpp b/explorer/interpreter/resolve_unformed.cpp index 2a4a118f1f2b..bcd3d15a7217 100644 --- a/explorer/interpreter/resolve_unformed.cpp +++ b/explorer/interpreter/resolve_unformed.cpp @@ -303,6 +303,7 @@ static auto ResolveUnformed(Nonnull declaration) case DeclarationKind::MixDeclaration: case DeclarationKind::MixinDeclaration: case DeclarationKind::InterfaceDeclaration: + case DeclarationKind::ConstraintDeclaration: case DeclarationKind::ImplDeclaration: case DeclarationKind::ChoiceDeclaration: case DeclarationKind::VariableDeclaration: diff --git a/explorer/interpreter/type_checker.cpp b/explorer/interpreter/type_checker.cpp index 62d0500967c4..2cfdab64dc47 100644 --- a/explorer/interpreter/type_checker.cpp +++ b/explorer/interpreter/type_checker.cpp @@ -138,6 +138,7 @@ static auto IsTypeOfType(Nonnull value) -> bool { return false; case Value::Kind::TypeType: case Value::Kind::InterfaceType: + case Value::Kind::NamedConstraintType: case Value::Kind::ConstraintType: // A value of one of these types is itself always a type. return true; @@ -182,6 +183,7 @@ static auto IsType(Nonnull value) -> bool { case Value::Kind::TupleType: case Value::Kind::NominalClassType: case Value::Kind::InterfaceType: + case Value::Kind::NamedConstraintType: case Value::Kind::ConstraintType: case Value::Kind::ChoiceType: case Value::Kind::ContinuationType: @@ -281,6 +283,12 @@ static auto ExpectCompleteType(SourceLocation source_loc, } break; } + case Value::Kind::NamedConstraintType: { + if (cast(type)->declaration().is_declared()) { + return Success(); + } + break; + } case Value::Kind::InterfaceType: { if (cast(type)->declaration().is_declared()) { return Success(); @@ -348,6 +356,7 @@ static auto TypeContainsAuto(Nonnull type) -> bool { case Value::Kind::FunctionType: case Value::Kind::NominalClassType: case Value::Kind::InterfaceType: + case Value::Kind::NamedConstraintType: case Value::Kind::ConstraintType: case Value::Kind::ChoiceType: case Value::Kind::ContinuationType: @@ -494,6 +503,7 @@ auto TypeChecker::IsImplicitlyConvertible( break; case Value::Kind::TypeType: case Value::Kind::InterfaceType: + case Value::Kind::NamedConstraintType: case Value::Kind::ConstraintType: // A value of empty struct type implicitly converts to a type. if (cast(*source).fields().empty()) { @@ -548,6 +558,7 @@ auto TypeChecker::IsImplicitlyConvertible( } case Value::Kind::TypeType: case Value::Kind::InterfaceType: + case Value::Kind::NamedConstraintType: case Value::Kind::ConstraintType: { // A tuple value converts to a type if all of its fields do. bool all_types = true; @@ -571,6 +582,7 @@ auto TypeChecker::IsImplicitlyConvertible( } case Value::Kind::TypeType: case Value::Kind::InterfaceType: + case Value::Kind::NamedConstraintType: case Value::Kind::ConstraintType: // TODO: We can't tell whether the conversion to this type-of-type would // work, because that depends on the source value, and we only have its @@ -614,7 +626,11 @@ auto TypeChecker::ImplicitlyConvert(std::string_view context, /*allow_user_defined_conversions=*/false)) { // A type only implicitly converts to a constraint if there is an impl of // that constraint for that type in scope. - if (isa(destination)) { + // TODO: Instead of excluding the special case where the destination is + // `Type`, we should check if the source type has a subset of the + // constraints of the destination type. In that case, the source should not + // be required to be constant. + if (IsTypeOfType(destination) && !isa(destination)) { // First convert the source expression to type `Type`. CARBON_ASSIGN_OR_RETURN(Nonnull source_as_type, ImplicitlyConvert(context, impl_scope, source, @@ -1025,6 +1041,24 @@ auto TypeChecker::ArgumentDeduction::Deduce(Nonnull param, } return Success(); } + case Value::Kind::NamedConstraintType: { + const auto& param_constraint_type = cast(*param); + if (arg->kind() != Value::Kind::NamedConstraintType) { + return handle_non_deduced_type(); + } + const auto& arg_constraint_type = cast(*arg); + if (param_constraint_type.declaration().name() != + arg_constraint_type.declaration().name()) { + return handle_non_deduced_type(); + } + for (const auto& [ty, param_ty] : + param_constraint_type.bindings().args()) { + CARBON_RETURN_IF_ERROR( + Deduce(param_ty, arg_constraint_type.bindings().args().at(ty), + /*allow_implicit_conversion=*/false)); + } + return Success(); + } // For the following cases, we check the type matches. case Value::Kind::StaticArrayType: // TODO: We could deduce the array type from an array or tuple argument. @@ -1886,6 +1920,14 @@ auto TypeChecker::SubstituteImpl(const Bindings& bindings, substitute_into_bindings(&iface_type.bindings())); return new_iface_type; } + case Value::Kind::NamedConstraintType: { + const auto& constraint_type = cast(*type); + Nonnull new_constraint_type = + arena_->New( + &constraint_type.declaration(), + substitute_into_bindings(&constraint_type.bindings())); + return new_constraint_type; + } case Value::Kind::ConstraintType: { const auto& constraint = cast(*type); if (auto it = bindings.args().find(constraint.self_binding()); @@ -2110,27 +2152,6 @@ auto TypeChecker::MakeConstraintWitnessAccess(Nonnull witness, return ConstraintImplWitness::Make(arena_, witness, impl_offset); } -auto TypeChecker::MakeConstraintForInterface( - SourceLocation source_loc, Nonnull iface_type) const - -> ErrorOr> { - CARBON_RETURN_IF_ERROR( - ExpectCompleteType(source_loc, "constraint", iface_type)); - - auto constraint_type = iface_type->declaration().constraint_type(); - CARBON_CHECK(constraint_type) - << "complete interface should have a constraint type"; - - if (iface_type->bindings().empty()) { - return *constraint_type; - } - - ConstraintTypeBuilder builder(arena_, source_loc); - builder.AddAndSubstitute(*this, *constraint_type, builder.GetSelfType(), - builder.GetSelfWitness(), iface_type->bindings(), - /*add_lookup_contexts=*/true); - return std::move(builder).Build(); -} - auto TypeChecker::ConvertToConstraintType( SourceLocation source_loc, std::string_view context, Nonnull constraint) const @@ -2139,7 +2160,17 @@ auto TypeChecker::ConvertToConstraintType( return constraint_type; } if (const auto* iface_type = dyn_cast(constraint)) { - return MakeConstraintForInterface(source_loc, iface_type); + CARBON_RETURN_IF_ERROR( + ExpectCompleteType(source_loc, "constraint", iface_type)); + return cast(Substitute( + iface_type->bindings(), *iface_type->declaration().constraint_type())); + } + if (const auto* constraint_type = dyn_cast(constraint)) { + CARBON_RETURN_IF_ERROR( + ExpectCompleteType(source_loc, "constraint", constraint_type)); + return cast( + Substitute(constraint_type->bindings(), + *constraint_type->declaration().constraint_type())); } if (isa(constraint)) { // TODO: Should we build this once and cache it? @@ -2600,8 +2631,8 @@ auto TypeChecker::TypeCheckExp(Nonnull e, // the impl scope, to find the witness. CARBON_ASSIGN_OR_RETURN( Nonnull iface_constraint, - MakeConstraintForInterface(access.source_loc(), - result.interface)); + ConvertToConstraintType(access.source_loc(), "member access", + result.interface)); CARBON_ASSIGN_OR_RETURN( Nonnull witness, impl_scope.Resolve(iface_constraint, &object_type, @@ -2630,6 +2661,7 @@ auto TypeChecker::TypeCheckExp(Nonnull e, return Success(); } case Value::Kind::InterfaceType: + case Value::Kind::NamedConstraintType: case Value::Kind::ConstraintType: { // This case handles access to a class function from a constrained // type variable. If `T` is a type variable and `foo` is a class @@ -2653,8 +2685,8 @@ auto TypeChecker::TypeCheckExp(Nonnull e, } CARBON_ASSIGN_OR_RETURN( Nonnull iface_constraint, - MakeConstraintForInterface(access.source_loc(), - result.interface)); + ConvertToConstraintType(access.source_loc(), "member access", + result.interface)); CARBON_ASSIGN_OR_RETURN(Nonnull witness, impl_scope.Resolve(iface_constraint, type, e->source_loc(), *this)); @@ -2772,6 +2804,7 @@ auto TypeChecker::TypeCheckExp(Nonnull e, } } case Value::Kind::InterfaceType: + case Value::Kind::NamedConstraintType: case Value::Kind::ConstraintType: { CARBON_ASSIGN_OR_RETURN( ConstraintLookupResult result, @@ -2855,7 +2888,8 @@ auto TypeChecker::TypeCheckExp(Nonnull e, CARBON_ASSIGN_OR_RETURN( Nonnull iface_constraint, - MakeConstraintForInterface(access.source_loc(), *iface)); + ConvertToConstraintType(access.source_loc(), + "compound member access", *iface)); CARBON_ASSIGN_OR_RETURN(witness, impl_scope.Resolve(iface_constraint, *base_type, e->source_loc(), *this)); @@ -3222,8 +3256,8 @@ auto TypeChecker::TypeCheckExp(Nonnull e, // Currently the only kinds of parameterized entities we support are // types. CARBON_CHECK( - isa( - param_name.declaration())) + isa(param_name.declaration())) << "unknown type of ParameterizedEntityName for " << param_name; call.set_static_type(arena_->New()); call.set_value_category(ValueCategory::Let); @@ -3980,7 +4014,7 @@ auto TypeChecker::TypeCheckGenericBinding(GenericBinding& binding, } // Create an impl binding if we have a constraint. - if (isa(type)) { + if (IsTypeOfType(type) && !isa(type)) { CARBON_ASSIGN_OR_RETURN( Nonnull constraint, ConvertToConstraintType(binding.source_loc(), context, type)); @@ -4694,67 +4728,95 @@ auto TypeChecker::TypeCheckMixDeclaration( return Success(); } -auto TypeChecker::DeclareInterfaceDeclaration( - Nonnull iface_decl, const ScopeInfo& scope_info) - -> ErrorOr { +auto TypeChecker::DeclareConstraintTypeDeclaration( + Nonnull constraint_decl, + const ScopeInfo& scope_info) -> ErrorOr { + CARBON_CHECK( + isa(constraint_decl)) + << "unexpected kind of constraint type declaration"; + bool is_interface = isa(constraint_decl); + if (trace_stream_) { - **trace_stream_ << "** declaring interface " << iface_decl->name() << "\n"; + **trace_stream_ << "** declaring "; + constraint_decl->PrintID(**trace_stream_); + **trace_stream_ << "\n"; } - ImplScope iface_scope; - iface_scope.AddParent(scope_info.innermost_scope); + ImplScope constraint_scope; + constraint_scope.AddParent(scope_info.innermost_scope); - Nonnull iface_type; - if (iface_decl->params().has_value()) { - CARBON_RETURN_IF_ERROR(TypeCheckPattern(*iface_decl->params(), std::nullopt, - iface_scope, ValueCategory::Let)); + // Type-check the parameters and find the set of bindings that are in scope. + std::vector> bindings = scope_info.bindings; + if (constraint_decl->params().has_value()) { + CARBON_RETURN_IF_ERROR(TypeCheckPattern(*constraint_decl->params(), + std::nullopt, constraint_scope, + ValueCategory::Let)); if (trace_stream_) { - **trace_stream_ << iface_scope; + **trace_stream_ << constraint_scope; } - - Nonnull param_name = - arena_->New(iface_decl, *iface_decl->params()); - iface_decl->set_static_type( - arena_->New(param_name)); - iface_decl->set_constant_value(param_name); - - // Form the full symbolic type of the interface. This is used as part of - // the value of associated constants, if they're referenced within the - // interface itself. - std::vector> bindings = scope_info.bindings; - CollectGenericBindingsInPattern(*iface_decl->params(), bindings); - iface_type = arena_->New( - iface_decl, Bindings::SymbolicIdentity(arena_, bindings)); - } else { - iface_type = arena_->New(iface_decl); - iface_decl->set_static_type(arena_->New()); - iface_decl->set_constant_value(iface_type); + CollectGenericBindingsInPattern(*constraint_decl->params(), bindings); } - // Set the type of Self to be the instantiated interface. - Nonnull self_type = iface_decl->self_type(); + // Form the full symbolic type of the interface or named constraint. This is + // used as part of the value of associated constants, if they're referenced + // within their interface, and as the symbolic value of the declaration. + Nonnull constraint_type; + if (is_interface) { + constraint_type = arena_->New( + cast(constraint_decl), + Bindings::SymbolicIdentity(arena_, bindings)); + } else { + constraint_type = arena_->New( + cast(constraint_decl), + Bindings::SymbolicIdentity(arena_, bindings)); + } + + // Set up the meaning of the declaration when used as an identifier. + if (constraint_decl->params().has_value()) { + Nonnull param_name = + arena_->New(constraint_decl, + *constraint_decl->params()); + constraint_decl->set_static_type( + arena_->New(param_name)); + constraint_decl->set_constant_value(param_name); + } else { + constraint_decl->set_static_type(arena_->New()); + constraint_decl->set_constant_value(constraint_type); + } + + // Set the type of Self to be the instantiated constraint type. + Nonnull self_type = constraint_decl->self_type(); self_type->set_static_type(arena_->New()); - self_type->set_constant_value(iface_type); + self_type->set_constant_value(constraint_type); - // Build a constraint corresponding to this interface. - ConstraintTypeBuilder::PrepareSelfBinding(arena_, iface_decl->self()); - ConstraintTypeBuilder builder(arena_, iface_decl->self()); + // Build a constraint corresponding to this constraint type. + ConstraintTypeBuilder::PrepareSelfBinding(arena_, constraint_decl->self()); + ConstraintTypeBuilder builder(arena_, constraint_decl->self()); ConstraintTypeBuilder::ConstraintsInScopeTracker constraint_tracker; - iface_decl->self()->set_static_type(iface_type); + constraint_decl->self()->set_static_type(constraint_type); - // The impl constraint says only that the direct members of the interface are - // available. For any indirect constraints, we need to add separate entries - // to the constraint type. This ensures that all indirect constraints are - // lifted to the top level so they can be accessed directly and resolved - // independently if necessary. - int index = builder.AddImplConstraint( - {.type = builder.GetSelfType(), .interface = iface_type}); - builder.AddLookupContext({.context = iface_type}); - const auto* impl_witness = - MakeConstraintWitnessAccess(builder.GetSelfWitness(), index); + // Lookups into this constraint type look in this declaration. + builder.AddLookupContext({.context = constraint_type}); - ScopeInfo iface_scope_info = ScopeInfo::ForNonClassScope(&iface_scope); - for (Nonnull m : iface_decl->members()) { - CARBON_RETURN_IF_ERROR(DeclareDeclaration(m, iface_scope_info)); + // If this is an interface, this is a symbolic witness that Self implements + // this interface. + std::optional> iface_impl_witness; + if (is_interface) { + // The impl constraint says only that the direct members of the interface + // are available. For any indirect constraints, we need to add separate + // entries to the constraint type. This ensures that all indirect + // constraints are lifted to the top level so they can be accessed directly + // and resolved independently if necessary. + int index = builder.AddImplConstraint( + {.type = builder.GetSelfType(), + .interface = cast(constraint_type)}); + iface_impl_witness = + MakeConstraintWitnessAccess(builder.GetSelfWitness(), index); + } + + ScopeInfo constraint_scope_info = + ScopeInfo::ForNonClassScope(&constraint_scope); + for (Nonnull m : constraint_decl->members()) { + CARBON_RETURN_IF_ERROR(DeclareDeclaration(m, constraint_scope_info)); // TODO: This should probably live in `DeclareDeclaration`, but it needs // to update state that's not available from there. @@ -4762,8 +4824,9 @@ auto TypeChecker::DeclareInterfaceDeclaration( case DeclarationKind::InterfaceExtendsDeclaration: { // For an `extends C;` declaration, add `Self is C` to our constraint. auto* extends = cast(m); - CARBON_ASSIGN_OR_RETURN(Nonnull base, - TypeCheckTypeExp(extends->base(), iface_scope)); + CARBON_ASSIGN_OR_RETURN( + Nonnull base, + TypeCheckTypeExp(extends->base(), constraint_scope)); CARBON_ASSIGN_OR_RETURN( Nonnull constraint_type, ConvertToConstraintType(m->source_loc(), "extends declaration", @@ -4779,10 +4842,10 @@ auto TypeChecker::DeclareInterfaceDeclaration( auto* impl = cast(m); CARBON_ASSIGN_OR_RETURN( Nonnull impl_type, - TypeCheckTypeExp(impl->impl_type(), iface_scope)); + TypeCheckTypeExp(impl->impl_type(), constraint_scope)); CARBON_ASSIGN_OR_RETURN( Nonnull constraint, - TypeCheckTypeExp(impl->constraint(), iface_scope)); + TypeCheckTypeExp(impl->constraint(), constraint_scope)); CARBON_ASSIGN_OR_RETURN( Nonnull constraint_type, ConvertToConstraintType(m->source_loc(), "impl as declaration", @@ -4795,24 +4858,31 @@ auto TypeChecker::DeclareInterfaceDeclaration( case DeclarationKind::AssociatedConstantDeclaration: { auto* assoc = cast(m); + if (!is_interface) { + // TODO: Template constraints can have associated constants. + return ProgramError(assoc->source_loc()) + << "associated constant not permitted in named constraint"; + } + CARBON_RETURN_IF_ERROR(TypeCheckGenericBinding( - assoc->binding(), "associated constant", iface_scope)); + assoc->binding(), "associated constant", constraint_scope)); Nonnull constraint = &assoc->binding().static_type(); assoc->set_static_type(constraint); // The constant value is used if the constant is named later in the - // same interface. Note that this differs from the symbolic identity of - // the binding, which was set in TypeCheckGenericBinding to a - // VariableType naming the binding so that .Self resolves to the + // same constraint type. Note that this differs from the symbolic + // identity of the binding, which was set in TypeCheckGenericBinding to + // a VariableType naming the binding so that .Self resolves to the // binding itself. auto* assoc_value = arena_->New( - &iface_decl->self()->value(), iface_type, assoc, impl_witness); + &constraint_decl->self()->value(), + cast(constraint_type), assoc, *iface_impl_witness); assoc->set_constant_value(assoc_value); // The type specified for the associated constant becomes a - // constraint for the interface: `let X:! Interface` adds a `Self.X - // is Interface` constraint that `impl`s must satisfy and users of - // the interface can rely on. + // constraint for the constraint type: `let X:! Interface` adds a + // `Self.X is Interface` constraint that `impl`s must satisfy and users + // of the constraint type can rely on. if (auto* constraint_type = dyn_cast(constraint)) { builder.AddAndSubstitute(*this, constraint_type, assoc_value, builder.GetSelfWitness(), Bindings(), @@ -4821,44 +4891,62 @@ auto TypeChecker::DeclareInterfaceDeclaration( break; } + case DeclarationKind::FunctionDeclaration: { + if (!is_interface) { + // TODO: Template constraints can have associated functions. + return ProgramError(m->source_loc()) + << "associated function not permitted in named constraint"; + } + break; + } + default: { + CARBON_FATAL() + << "unexpected declaration in constraint type declaration:\n" + << *m; break; } } // Add any new impl constraints to the scope. - builder.BringConstraintsIntoScope(*this, &iface_scope, &constraint_tracker); + builder.BringConstraintsIntoScope(*this, &constraint_scope, + &constraint_tracker); } - iface_decl->set_constraint_type(std::move(builder).Build()); + constraint_decl->set_constraint_type(std::move(builder).Build()); if (trace_stream_) { - **trace_stream_ << "** finished declaring interface " << iface_decl->name() - << "\n"; + **trace_stream_ << "** finished declaring "; + constraint_decl->PrintID(**trace_stream_); + **trace_stream_ << "\n"; } return Success(); } -auto TypeChecker::TypeCheckInterfaceDeclaration( - Nonnull iface_decl, const ImplScope& impl_scope) - -> ErrorOr { +auto TypeChecker::TypeCheckConstraintTypeDeclaration( + Nonnull constraint_decl, + const ImplScope& impl_scope) -> ErrorOr { if (trace_stream_) { - **trace_stream_ << "** checking interface " << iface_decl->name() << "\n"; + **trace_stream_ << "** checking "; + constraint_decl->PrintID(**trace_stream_); + **trace_stream_ << "\n"; } - ImplScope iface_scope; - iface_scope.AddParent(&impl_scope); - if (iface_decl->params().has_value()) { - BringPatternImplsIntoScope(*iface_decl->params(), iface_scope); + ImplScope constraint_scope; + constraint_scope.AddParent(&impl_scope); + if (constraint_decl->params().has_value()) { + BringPatternImplsIntoScope(*constraint_decl->params(), constraint_scope); } if (trace_stream_) { - **trace_stream_ << iface_scope; + **trace_stream_ << constraint_scope; } - for (Nonnull m : iface_decl->members()) { - CARBON_RETURN_IF_ERROR(TypeCheckDeclaration(m, iface_scope, iface_decl)); + for (Nonnull m : constraint_decl->members()) { + CARBON_RETURN_IF_ERROR( + TypeCheckDeclaration(m, constraint_scope, constraint_decl)); } if (trace_stream_) { - **trace_stream_ << "** finished checking interface " << iface_decl->name() - << "\n"; + **trace_stream_ << "** finished checking "; + constraint_decl->PrintID(**trace_stream_); + **trace_stream_ << "\n"; } return Success(); } @@ -4964,7 +5052,8 @@ auto TypeChecker::CheckAndAddImplBindings( *this); CARBON_ASSIGN_OR_RETURN( Nonnull iface_constraint, - MakeConstraintForInterface(impl_decl->source_loc(), iface_type)); + ConvertToConstraintType(impl_decl->source_loc(), "impl declaration", + iface_type)); CARBON_ASSIGN_OR_RETURN( iface_witness, impl_scope.Resolve(iface_constraint, impl_type, impl_decl->source_loc(), *this)); @@ -4980,6 +5069,9 @@ auto TypeChecker::CheckAndAddImplBindings( scope_info.innermost_non_class_scope->Add( iface_type, deduced_bindings, impl_type, impl_decl->impl_bindings(), self_witness, *this); + } else if (isa(lookup.context)) { + // Nothing to check here, since a named constraint can't introduce any + // associated entities. } else { // TODO: Add support for implementing `adapter`s. return ProgramError(impl_decl->source_loc()) @@ -5278,6 +5370,7 @@ static auto IsValidTypeForAliasTarget(Nonnull type) -> bool { case Value::Kind::FunctionType: case Value::Kind::InterfaceType: + case Value::Kind::NamedConstraintType: case Value::Kind::ConstraintType: case Value::Kind::TypeType: case Value::Kind::TypeOfParameterizedEntityName: @@ -5334,9 +5427,10 @@ auto TypeChecker::TypeCheckDeclaration( **trace_stream_ << "checking " << DeclarationKindName(d->kind()) << "\n"; } switch (d->kind()) { - case DeclarationKind::InterfaceDeclaration: { - CARBON_RETURN_IF_ERROR(TypeCheckInterfaceDeclaration( - &cast(*d), impl_scope)); + case DeclarationKind::InterfaceDeclaration: + case DeclarationKind::ConstraintDeclaration: { + CARBON_RETURN_IF_ERROR(TypeCheckConstraintTypeDeclaration( + &cast(*d), impl_scope)); break; } case DeclarationKind::ImplDeclaration: { @@ -5391,7 +5485,7 @@ auto TypeChecker::TypeCheckDeclaration( case DeclarationKind::InterfaceExtendsDeclaration: case DeclarationKind::InterfaceImplDeclaration: case DeclarationKind::AssociatedConstantDeclaration: { - // Checked in DeclareInterfaceDeclaration. + // Checked in DeclareConstraintTypeDeclaration. break; } case DeclarationKind::SelfDeclaration: { @@ -5409,10 +5503,11 @@ auto TypeChecker::DeclareDeclaration(Nonnull d, const ScopeInfo& scope_info) -> ErrorOr { switch (d->kind()) { - case DeclarationKind::InterfaceDeclaration: { - auto& iface_decl = cast(*d); + case DeclarationKind::InterfaceDeclaration: + case DeclarationKind::ConstraintDeclaration: { + auto& iface_decl = cast(*d); CARBON_RETURN_IF_ERROR( - DeclareInterfaceDeclaration(&iface_decl, scope_info)); + DeclareConstraintTypeDeclaration(&iface_decl, scope_info)); break; } case DeclarationKind::ImplDeclaration: { @@ -5481,7 +5576,7 @@ auto TypeChecker::DeclareDeclaration(Nonnull d, case DeclarationKind::InterfaceExtendsDeclaration: case DeclarationKind::InterfaceImplDeclaration: case DeclarationKind::AssociatedConstantDeclaration: { - // The semantic effects are handled by DeclareInterfaceDeclaration. + // The semantic effects are handled by DeclareConstraintTypeDeclaration. break; } diff --git a/explorer/interpreter/type_checker.h b/explorer/interpreter/type_checker.h index d3e0e762c306..6dbcc768a18f 100644 --- a/explorer/interpreter/type_checker.h +++ b/explorer/interpreter/type_checker.h @@ -210,9 +210,9 @@ class TypeChecker { auto DeclareMixinDeclaration(Nonnull mixin_decl, const ScopeInfo& scope_info) -> ErrorOr; - auto DeclareInterfaceDeclaration(Nonnull iface_decl, - const ScopeInfo& scope_info) - -> ErrorOr; + auto DeclareConstraintTypeDeclaration( + Nonnull constraint_decl, + const ScopeInfo& scope_info) -> ErrorOr; // Check that the deduced parameters of an impl are actually deducible from // the form of the interface, for a declaration of the form @@ -307,10 +307,10 @@ class TypeChecker { std::optional> enclosing_decl) -> ErrorOr; - // Type check all the members of the interface. - auto TypeCheckInterfaceDeclaration(Nonnull iface_decl, - const ImplScope& impl_scope) - -> ErrorOr; + // Type check all the members of the interface or named constraint. + auto TypeCheckConstraintTypeDeclaration( + Nonnull constraint_decl, + const ImplScope& impl_scope) -> ErrorOr; // Bring the associated constants in `constraint` that constrain the // implementation of `interface` for `self` into `scope`. @@ -428,12 +428,6 @@ class TypeChecker { BuiltinInterfaceName interface) const -> ErrorOr>; - // Given an interface type, form a corresponding constraint type. The - // interface must be a complete type. - auto MakeConstraintForInterface( - SourceLocation source_loc, Nonnull iface_type) const - -> ErrorOr>; - // Convert a value that is expected to represent a constraint into a // `ConstraintType`. auto ConvertToConstraintType(SourceLocation source_loc, diff --git a/explorer/interpreter/value.cpp b/explorer/interpreter/value.cpp index a5dce83235d9..a476ce9a63b1 100644 --- a/explorer/interpreter/value.cpp +++ b/explorer/interpreter/value.cpp @@ -423,12 +423,19 @@ void Value::Print(llvm::raw_ostream& out) const { case Value::Kind::InterfaceType: { const auto& iface_type = cast(*this); out << "interface "; - PrintNameWithBindings(out, &iface_type.declaration(), iface_type.args()); + PrintNameWithBindings(out, &iface_type.declaration(), + iface_type.bindings().args()); + break; + } + case Value::Kind::NamedConstraintType: { + const auto& constraint_type = cast(*this); + out << "constraint "; + PrintNameWithBindings(out, &constraint_type.declaration(), + constraint_type.bindings().args()); break; } case Value::Kind::ConstraintType: { const auto& constraint = cast(*this); - out << "constraint "; llvm::ListSeparator combine(" & "); for (const LookupContext& ctx : constraint.lookup_contexts()) { out << combine << *ctx.context; @@ -653,14 +660,23 @@ auto TypeEqual(Nonnull t1, Nonnull t2, const auto& class1 = cast(*t1); const auto& class2 = cast(*t2); return class1.declaration().name() == class2.declaration().name() && - BindingMapEqual(class1.type_args(), class2.type_args(), + BindingMapEqual(class1.bindings().args(), class2.bindings().args(), equality_ctx); } case Value::Kind::InterfaceType: { const auto& iface1 = cast(*t1); const auto& iface2 = cast(*t2); return iface1.declaration().name() == iface2.declaration().name() && - BindingMapEqual(iface1.args(), iface2.args(), equality_ctx); + BindingMapEqual(iface1.bindings().args(), iface2.bindings().args(), + equality_ctx); + } + case Value::Kind::NamedConstraintType: { + const auto& constraint1 = cast(*t1); + const auto& constraint2 = cast(*t2); + return constraint1.declaration().name() == + constraint2.declaration().name() && + BindingMapEqual(constraint1.bindings().args(), + constraint2.bindings().args(), equality_ctx); } case Value::Kind::AssociatedConstant: // Associated constants are sometimes types. @@ -869,6 +885,7 @@ auto ValueStructurallyEqual( case Value::Kind::NominalClassType: case Value::Kind::MixinPseudoType: case Value::Kind::InterfaceType: + case Value::Kind::NamedConstraintType: case Value::Kind::ConstraintType: case Value::Kind::ImplWitness: case Value::Kind::BindingWitness: diff --git a/explorer/interpreter/value.h b/explorer/interpreter/value.h index 40ac282e939d..90bbfd8e2b04 100644 --- a/explorer/interpreter/value.h +++ b/explorer/interpreter/value.h @@ -64,6 +64,7 @@ class Value { TupleType, MixinPseudoType, InterfaceType, + NamedConstraintType, ConstraintType, ChoiceType, ContinuationType, // The type of a continuation. @@ -772,6 +773,31 @@ class InterfaceType : public Value { Nonnull bindings_ = Bindings::None(); }; +// A named constraint type. +class NamedConstraintType : public Value { + public: + explicit NamedConstraintType( + Nonnull declaration, + Nonnull bindings) + : Value(Kind::NamedConstraintType), + declaration_(declaration), + bindings_(bindings) {} + + static auto classof(const Value* value) -> bool { + return value->kind() == Kind::NamedConstraintType; + } + + auto declaration() const -> const ConstraintDeclaration& { + return *declaration_; + } + + auto bindings() const -> const Bindings& { return *bindings_; } + + private: + Nonnull declaration_; + Nonnull bindings_ = Bindings::None(); +}; + // A constraint that requires implementation of an interface. struct ImplConstraint { // The type that is required to implement the interface. diff --git a/explorer/syntax/lexer.lpp b/explorer/syntax/lexer.lpp index 593edd4f762d..1f21202f9ae7 100644 --- a/explorer/syntax/lexer.lpp +++ b/explorer/syntax/lexer.lpp @@ -51,6 +51,7 @@ CLASS "class" COLON ":" COLON_BANG ":!" COMMA "," +CONSTRAINT "constraint" CONTINUATION "__continuation" CONTINUATION_TYPE "__Continuation" CONTINUE "continue" @@ -157,6 +158,7 @@ operand_start [(A-Za-z0-9_\"] {COLON_BANG} { return CARBON_SIMPLE_TOKEN(COLON_BANG); } {COLON} { return CARBON_SIMPLE_TOKEN(COLON); } {COMMA} { return CARBON_SIMPLE_TOKEN(COMMA); } +{CONSTRAINT} { return CARBON_SIMPLE_TOKEN(CONSTRAINT); } {CONTINUATION_TYPE} { return CARBON_SIMPLE_TOKEN(CONTINUATION_TYPE); } {CONTINUATION} { return CARBON_SIMPLE_TOKEN(CONTINUATION); } {CONTINUE} { return CARBON_SIMPLE_TOKEN(CONTINUE); } diff --git a/explorer/syntax/parser.ypp b/explorer/syntax/parser.ypp index 6d7db09249a7..c926a2aac3a6 100644 --- a/explorer/syntax/parser.ypp +++ b/explorer/syntax/parser.ypp @@ -219,6 +219,7 @@ COLON COLON_BANG COMMA + CONSTRAINT CONTINUATION CONTINUATION_TYPE CONTINUE @@ -1177,6 +1178,11 @@ declaration: $$ = arena->New(arena, context.source_loc(), $2, $3, $5); } +| CONSTRAINT identifier type_params LEFT_CURLY_BRACE interface_body RIGHT_CURLY_BRACE + { + $$ = arena->New(arena, context.source_loc(), $2, + $3, $5); + } | impl_kind IMPL impl_deduced_params impl_type AS type_or_where_expression LEFT_CURLY_BRACE impl_body RIGHT_CURLY_BRACE { ErrorOr impl = ImplDeclaration::Create( diff --git a/explorer/testdata/constraint/fail_combine_equality.carbon b/explorer/testdata/constraint/fail_combine_equality.carbon index eac46b86e94f..20bfc09b9018 100644 --- a/explorer/testdata/constraint/fail_combine_equality.carbon +++ b/explorer/testdata/constraint/fail_combine_equality.carbon @@ -13,7 +13,7 @@ impl i32 as I {} fn F(A:! i32, B:! i32, C:! i32, D:! i32, E:! i32, T:! I where A == B and C == D and C == E and B == D) { - // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/constraint/fail_combine_equality.carbon:[[@LINE+1]]: member access, F not in constraint interface I where T is interface I and A == B and C == D and C == E and B == D + // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/constraint/fail_combine_equality.carbon:[[@LINE+1]]: member access, F not in interface I where T is interface I and A == B and C == D and C == E and B == D T.F(); } diff --git a/explorer/testdata/constraint/fail_missing_member.carbon b/explorer/testdata/constraint/fail_missing_member.carbon index 11ee0a5d8a41..a641a9671d19 100644 --- a/explorer/testdata/constraint/fail_missing_member.carbon +++ b/explorer/testdata/constraint/fail_missing_member.carbon @@ -11,7 +11,7 @@ package ExplorerTest api; interface A { fn F() -> i32; } interface B { fn G() -> i32; } -// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/constraint/fail_missing_member.carbon:[[@LINE+1]]: member access, H not in constraint interface A & interface B where T is interface A and T is interface B +// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/constraint/fail_missing_member.carbon:[[@LINE+1]]: member access, H not in interface A & interface B where T is interface A and T is interface B fn Get[T:! A & B](n: T) -> i32 { return n.H(); } impl i32 as A { diff --git a/explorer/testdata/function/auto_return/fail_multiple_returns.carbon b/explorer/testdata/function/auto_return/fail_multiple_returns.carbon index 4c8c4ff02409..e1981d602032 100644 --- a/explorer/testdata/function/auto_return/fail_multiple_returns.carbon +++ b/explorer/testdata/function/auto_return/fail_multiple_returns.carbon @@ -8,7 +8,7 @@ package ExplorerTest api; -fn Add(x: i32, y: i32) -> auto { +fn ComputeSum(x: i32, y: i32) -> auto { if (x == 0) { return x; } else if (y == 0) { @@ -20,5 +20,5 @@ fn Add(x: i32, y: i32) -> auto { } fn Main() -> i32 { - return Add(1, 2) - 3; + return ComputeSum(1, 2) - 3; } diff --git a/explorer/testdata/function/auto_return/fail_separate_decl.carbon b/explorer/testdata/function/auto_return/fail_separate_decl.carbon index a677296d4cac..ec58f907a7fd 100644 --- a/explorer/testdata/function/auto_return/fail_separate_decl.carbon +++ b/explorer/testdata/function/auto_return/fail_separate_decl.carbon @@ -10,8 +10,8 @@ package ExplorerTest api; // This declaration is not allowed. // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/function/auto_return/fail_separate_decl.carbon:[[@LINE+1]]: Function declaration has deduced return type but no body -fn Add(x: i32, y: i32) -> auto; +fn ComputeSum(x: i32, y: i32) -> auto; fn Main() -> i32 { - return Add(1, 2) - 3; + return ComputeSum(1, 2) - 3; } diff --git a/explorer/testdata/interface/fail_interface_missing_member.carbon b/explorer/testdata/interface/fail_interface_missing_member.carbon index b998ea533ce9..4ab988c89734 100644 --- a/explorer/testdata/interface/fail_interface_missing_member.carbon +++ b/explorer/testdata/interface/fail_interface_missing_member.carbon @@ -13,7 +13,7 @@ interface Vector { } fn ScaleGeneric[T:! Vector](a: T, s: i32) -> T { - // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/interface/fail_interface_missing_member.carbon:[[@LINE+1]]: member access, Scale not in constraint interface Vector where T is interface Vector + // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/interface/fail_interface_missing_member.carbon:[[@LINE+1]]: member access, Scale not in interface Vector where T is interface Vector return a.Scale(s); } diff --git a/explorer/testdata/named_constraint/extends.carbon b/explorer/testdata/named_constraint/extends.carbon new file mode 100644 index 000000000000..76d703d8b2fa --- /dev/null +++ b/explorer/testdata/named_constraint/extends.carbon @@ -0,0 +1,53 @@ +// 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 +// +// AUTOUPDATE +// RUN: %{explorer-run} +// RUN: %{explorer-run-trace} +// CHECK:STDOUT: 5 +// CHECK:STDOUT: 5 +// CHECK:STDOUT: 5 +// CHECK:STDOUT: 5 +// CHECK:STDOUT: 5 +// CHECK:STDOUT: result: 0 + +package ExplorerTest api; + +interface Maker(T:! Type) { + fn Make[me: Self]() -> T; +} + +constraint IndirectMaker(T:! Type) { + extends Maker(T*); +} + +constraint MoreIndirectMaker { + extends IndirectMaker(i32); +} + +class PointerFactory { + var p: i32*; + external impl as Maker(i32*) { + fn Make[me: Self]() -> i32* { return me.p; } + } +} + +fn CallIndirect[T:! IndirectMaker(i32)](x: T) -> i32 { + return *x.Make(); +} + +fn CallMoreIndirect[T:! MoreIndirectMaker](x: T) -> i32 { + return *x.Make(); +} + +fn Main() -> i32 { + var n: i32 = 5; + var f: PointerFactory = {.p = &n}; + Print("{0}", *f.(Maker(i32*).Make)()); + Print("{0}", *f.(IndirectMaker(i32).Make)()); + Print("{0}", *f.(MoreIndirectMaker.Make)()); + Print("{0}", CallIndirect(f)); + Print("{0}", CallMoreIndirect(f)); + return 0; +} diff --git a/explorer/testdata/named_constraint/fail_associated_constant.carbon b/explorer/testdata/named_constraint/fail_associated_constant.carbon new file mode 100644 index 000000000000..77b6e8a72c45 --- /dev/null +++ b/explorer/testdata/named_constraint/fail_associated_constant.carbon @@ -0,0 +1,16 @@ +// 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 +// +// AUTOUPDATE +// RUN: %{not} %{explorer-run} +// RUN: %{not} %{explorer-run-trace} + +package ExplorerTest api; + +constraint X { + // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/named_constraint/fail_associated_constant.carbon:[[@LINE+1]]: associated constant not permitted in named constraint + let N:! Type; +} + +fn Main() -> i32 { return 0; } diff --git a/explorer/testdata/named_constraint/fail_associated_function.carbon b/explorer/testdata/named_constraint/fail_associated_function.carbon new file mode 100644 index 000000000000..972e4ebddb0a --- /dev/null +++ b/explorer/testdata/named_constraint/fail_associated_function.carbon @@ -0,0 +1,16 @@ +// 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 +// +// AUTOUPDATE +// RUN: %{not} %{explorer-run} +// RUN: %{not} %{explorer-run-trace} + +package ExplorerTest api; + +constraint X { + // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/named_constraint/fail_associated_function.carbon:[[@LINE+1]]: associated function not permitted in named constraint + fn F(); +} + +fn Main() -> i32 { return 0; } diff --git a/explorer/testdata/named_constraint/fail_compound_member_access.carbon b/explorer/testdata/named_constraint/fail_compound_member_access.carbon new file mode 100644 index 000000000000..4a9e5a738cf9 --- /dev/null +++ b/explorer/testdata/named_constraint/fail_compound_member_access.carbon @@ -0,0 +1,32 @@ +// 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 +// +// AUTOUPDATE +// RUN: %{not} %{explorer-run} +// RUN: %{not} %{explorer-run-trace} + +package ExplorerTest api; + +interface MyAddWith(T:! Type) { + let Result:! Type; + fn Op[me: Self](other: T) -> Result; +} + +constraint MyAdd { + extends MyAddWith(Self) where .Result = Self; +} + +external impl i32 as MyAdd { + fn Op[me: i32](other: i32) -> i32 { return me + other; } +} + +fn Main() -> i32 { + let n: i32 = 1; + // TODO: This should be valid, but isn't representable in our current + // MemberName value. We will likely need MemberName to remember the + // constraint as written, not only the interface in which the member was + // found. + // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/named_constraint/fail_compound_member_access.carbon:[[@LINE+1]]: could not find implementation of interface MyAddWith(T = Self) for i32 + return n.(MyAdd.Op)(n); +} diff --git a/explorer/testdata/named_constraint/fail_impl_as_member_access.carbon b/explorer/testdata/named_constraint/fail_impl_as_member_access.carbon new file mode 100644 index 000000000000..ebb612dfe4a0 --- /dev/null +++ b/explorer/testdata/named_constraint/fail_impl_as_member_access.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 +// +// AUTOUPDATE +// RUN: %{not} %{explorer-run} +// RUN: %{not} %{explorer-run-trace} + +package ExplorerTest api; + +interface HasFoo { + fn Foo(); +} + +constraint ImplAsHasFoo { + impl as HasFoo; +} + +fn CallFoo[T:! ImplAsHasFoo](x: T) { + // OK, T is HasFoo. + x.(HasFoo.Foo)(); + // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/named_constraint/fail_impl_as_member_access.carbon:[[@LINE+1]]: member access, Foo not in constraint ImplAsHasFoo where T is interface HasFoo + x.Foo(); +} + +fn Main() -> i32 { return 0; } diff --git a/explorer/testdata/named_constraint/impl_as.carbon b/explorer/testdata/named_constraint/impl_as.carbon new file mode 100644 index 000000000000..d54819fd7e52 --- /dev/null +++ b/explorer/testdata/named_constraint/impl_as.carbon @@ -0,0 +1,41 @@ +// 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 +// +// AUTOUPDATE +// RUN: %{explorer-run} +// RUN: %{explorer-run-trace} +// CHECK:STDOUT: result: 4 + +package ExplorerTest api; + +interface MyHashable { + let Result:! Type; + fn Hash[me: Self]() -> Result; +} + +constraint HashToIntConvertible { + impl as MyHashable; + impl Self.(MyHashable.Result) as ImplicitAs(i32); +} + +class MyHashValue { + external impl as ImplicitAs(i32) { + fn Convert[me: Self]() -> i32 { return 4; } + } +} + +class Widget { + external impl as MyHashable where .Result = MyHashValue { + fn Hash[me: Self]() -> MyHashValue { return {}; } + } +} + +fn MakeSmallHash[T:! HashToIntConvertible](x: T) -> i32 { + return x.(MyHashable.Hash)(); +} + +fn Main() -> i32 { + var w: Widget = {}; + return MakeSmallHash(w); +} diff --git a/explorer/testdata/named_constraint/impl_constraint.carbon b/explorer/testdata/named_constraint/impl_constraint.carbon new file mode 100644 index 000000000000..3997f6ba6728 --- /dev/null +++ b/explorer/testdata/named_constraint/impl_constraint.carbon @@ -0,0 +1,37 @@ +// 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 +// +// AUTOUPDATE +// RUN: %{explorer-run} +// RUN: %{explorer-run-trace} +// CHECK:STDOUT: result: 12 + +package ExplorerTest api; + +interface Runnable { + fn Run[me: Self]() -> i32; +} + +interface Walkable { + fn Walk[me: Self]() -> i32; +} + +constraint Traversible { + extends Runnable; + extends Walkable; +} + +external impl i32 as Traversible { + fn Run[me: i32]() -> i32 { + return 10 * me; + } + fn Walk[me: i32]() -> i32 { + return me + 1; + } +} + +fn Main() -> i32 { + var n: i32 = 1; + return n.(Runnable.Run)() + n.(Walkable.Walk)(); +} diff --git a/explorer/testdata/operators/add.carbon b/explorer/testdata/operators/add.carbon index 42d1601c5b99..621c9fa52abb 100644 --- a/explorer/testdata/operators/add.carbon +++ b/explorer/testdata/operators/add.carbon @@ -5,18 +5,18 @@ // AUTOUPDATE // RUN: %{explorer-run} // RUN: %{explorer-run-trace} -// CHECK:STDOUT: result: 6 +// CHECK:STDOUT: result: 12 package ExplorerTest api; class A { var n: i32; } -external impl A as AddWith(i32) where .Result = A { - fn Op[me: Self](rhs: i32) -> A { return {.n = me.n + rhs}; } +external impl A as Add { + fn Op[me: Self](rhs: A) -> A { return {.n = me.n + rhs.n}; } } fn Main() -> i32 { var a: A = {.n = 5}; - a = a + 1; - return a.n; + var b: A = {.n = 7}; + return (a + b).n; } diff --git a/explorer/testdata/operators/add_with.carbon b/explorer/testdata/operators/add_with.carbon new file mode 100644 index 000000000000..42d1601c5b99 --- /dev/null +++ b/explorer/testdata/operators/add_with.carbon @@ -0,0 +1,22 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// AUTOUPDATE +// RUN: %{explorer-run} +// RUN: %{explorer-run-trace} +// CHECK:STDOUT: result: 6 + +package ExplorerTest api; + +class A { var n: i32; } + +external impl A as AddWith(i32) where .Result = A { + fn Op[me: Self](rhs: i32) -> A { return {.n = me.n + rhs}; } +} + +fn Main() -> i32 { + var a: A = {.n = 5}; + a = a + 1; + return a.n; +}