From 2fef76e58ca3522b93ddc5c6f5cb085377cc2f19 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Tue, 14 Jun 2022 21:49:01 -0700 Subject: [PATCH] Basic support for `.Self` within `:!` bindings and `where` expressions. (#1311) `.Self` is modeled as a new kind of expression, `DotSelfExpression`. Name resolution associates each occurrence of a `DotSelfExpression` with a generic binding, much like for an `IdentifierExpression`. Both `:!` bindings and `where` expressions bring `.Self` into scope. The tentative intent is that if there are multiple `.Self`s in scope and they refer to different bindings, the result of using a `.Self` expression is an ambiguity error, but that is not implemented in this patch. Instead, like for `IdentifierExpression`s, we find the innermost enclosing definition of `.Self`. `.Foo` expressions are rewritten to `.Self.Foo`, but this isn't enough to make them do anything useful, because associated constants aren't supported in general yet. --- common/fuzzing/carbon.proto | 5 ++ common/fuzzing/proto_to_carbon.cpp | 7 +++ explorer/ast/BUILD | 28 ++------- explorer/ast/ast_rtti.txt | 1 + explorer/ast/expression.cpp | 7 ++- explorer/ast/expression.h | 49 +++++++++++++-- explorer/ast/pattern.h | 16 +++++ explorer/ast/static_scope.h | 4 +- explorer/fuzzing/ast_to_proto.cpp | 17 +++++- explorer/interpreter/BUILD | 3 - explorer/interpreter/interpreter.cpp | 9 +++ explorer/interpreter/resolve_names.cpp | 31 +++++++--- explorer/interpreter/type_checker.cpp | 59 +++++++++++++------ explorer/syntax/BUILD | 1 - explorer/syntax/parser.ypp | 15 ++++- .../testdata/constraint/binding_self.carbon | 30 ++++++++++ .../constraint/combine_equality.carbon | 2 +- .../constraint/dot_self_is_other.carbon | 31 ++++++++++ .../fail_dot_self_after_scope.carbon | 22 +++++++ .../fail_dot_self_after_scope_2.carbon | 22 +++++++ .../fail_dot_self_not_in_scope.carbon | 16 +++++ .../constraint/fail_infinite_self.carbon | 16 +++++ .../constraint/fail_infinite_self_ptr.carbon | 16 +++++ .../constraint/fail_nontype_self.carbon | 18 ++++++ .../qualified_lookup_in_where.carbon | 26 ++++++++ .../testdata/constraint/where_self.carbon | 34 +++++++++++ 26 files changed, 418 insertions(+), 67 deletions(-) create mode 100644 explorer/testdata/constraint/binding_self.carbon create mode 100644 explorer/testdata/constraint/dot_self_is_other.carbon create mode 100644 explorer/testdata/constraint/fail_dot_self_after_scope.carbon create mode 100644 explorer/testdata/constraint/fail_dot_self_after_scope_2.carbon create mode 100644 explorer/testdata/constraint/fail_dot_self_not_in_scope.carbon create mode 100644 explorer/testdata/constraint/fail_infinite_self.carbon create mode 100644 explorer/testdata/constraint/fail_infinite_self_ptr.carbon create mode 100644 explorer/testdata/constraint/fail_nontype_self.carbon create mode 100644 explorer/testdata/constraint/qualified_lookup_in_where.carbon create mode 100644 explorer/testdata/constraint/where_self.carbon diff --git a/common/fuzzing/carbon.proto b/common/fuzzing/carbon.proto index 8ba1e4055ad7..350424e97ad8 100644 --- a/common/fuzzing/carbon.proto +++ b/common/fuzzing/carbon.proto @@ -79,6 +79,10 @@ message IdentifierExpression { optional string name = 1; } +message DesignatorExpression { + optional string name = 1; +} + message IntrinsicExpression { enum Intrinsic { UnknownIntrinsic = 0; @@ -170,6 +174,7 @@ message Expression { ArrayTypeLiteral array_type_literal = 21; CompoundMemberAccessExpression compound_member_access = 22; WhereExpression where = 23; + DesignatorExpression designator = 24; } } diff --git a/common/fuzzing/proto_to_carbon.cpp b/common/fuzzing/proto_to_carbon.cpp index 81df675a2440..e75ae8de1651 100644 --- a/common/fuzzing/proto_to_carbon.cpp +++ b/common/fuzzing/proto_to_carbon.cpp @@ -275,6 +275,13 @@ static auto ExpressionToCarbon(const Fuzzing::Expression& expression, break; } + case Fuzzing::Expression::kDesignator: { + const auto& designator = expression.designator(); + out << "."; + IdentifierToCarbon(designator.name(), out); + break; + } + case Fuzzing::Expression::kIntrinsic: { const auto& intrinsic = expression.intrinsic(); switch (intrinsic.intrinsic()) { diff --git a/explorer/ast/BUILD b/explorer/ast/BUILD index 279775eedb36..e993178a7535 100644 --- a/explorer/ast/BUILD +++ b/explorer/ast/BUILD @@ -64,7 +64,6 @@ cc_test( ":ast_test_matchers", ":declaration", ":expression", - ":pattern", ":statement", "//common:gtest_main", "//explorer/common:arena", @@ -79,7 +78,7 @@ cc_library( ], deps = [ ":ast_node", - ":pattern", + ":expression", ":value_category", "//common:check", "//common:ostream", @@ -98,7 +97,7 @@ cc_library( deps = [ ":ast_node", ":impl_binding", - ":pattern", + ":expression", ":return_term", ":statement", ":static_scope", @@ -125,8 +124,8 @@ cc_library( cc_library( name = "expression", - srcs = ["expression.cpp"], - hdrs = ["expression.h"], + srcs = ["expression.cpp", "pattern.cpp"], + hdrs = ["expression.h", "pattern.h"], deps = [ ":ast_node", ":paren_contents", @@ -168,30 +167,12 @@ cc_library( ], ) -cc_library( - name = "pattern", - srcs = ["pattern.cpp"], - hdrs = ["pattern.h"], - deps = [ - ":ast_node", - ":expression", - ":static_scope", - ":value_category", - "//common:ostream", - "//explorer/common:arena", - "//explorer/common:error_builders", - "//explorer/common:source_location", - "@llvm-project//llvm:Support", - ], -) - cc_test( name = "pattern_test", srcs = ["pattern_test.cpp"], deps = [ ":expression", ":paren_contents", - ":pattern", "//common:gtest_main", "//explorer/common:arena", "@com_google_googletest//:gtest", @@ -222,7 +203,6 @@ cc_library( deps = [ ":ast_node", ":expression", - ":pattern", ":return_term", ":static_scope", ":value_category", diff --git a/explorer/ast/ast_rtti.txt b/explorer/ast/ast_rtti.txt index 39f6b7fc9b87..61c38960540a 100644 --- a/explorer/ast/ast_rtti.txt +++ b/explorer/ast/ast_rtti.txt @@ -57,6 +57,7 @@ abstract class Expression : AstNode; class TypeTypeLiteral : Expression; class ValueLiteral : Expression; class IdentifierExpression : Expression; + class DotSelfExpression : Expression; class IntrinsicExpression : Expression; class IfExpression : Expression; class WhereExpression : Expression; diff --git a/explorer/ast/expression.cpp b/explorer/ast/expression.cpp index ea1dff70914a..bf05a9167294 100644 --- a/explorer/ast/expression.cpp +++ b/explorer/ast/expression.cpp @@ -7,6 +7,7 @@ #include #include +#include "explorer/ast/pattern.h" #include "explorer/common/arena.h" #include "explorer/common/error_builders.h" #include "llvm/ADT/StringExtras.h" @@ -184,7 +185,7 @@ void Expression::Print(llvm::raw_ostream& out) const { } case ExpressionKind::WhereExpression: { const auto& where = cast(*this); - out << where.base() << " where "; + out << where.self_binding().type() << " where "; llvm::ListSeparator sep(" and "); for (const WhereClause* clause : where.clauses()) { out << sep << *clause; @@ -213,6 +214,7 @@ void Expression::Print(llvm::raw_ostream& out) const { break; } case ExpressionKind::IdentifierExpression: + case ExpressionKind::DotSelfExpression: case ExpressionKind::IntLiteral: case ExpressionKind::BoolLiteral: case ExpressionKind::BoolTypeLiteral: @@ -232,6 +234,9 @@ void Expression::PrintID(llvm::raw_ostream& out) const { case ExpressionKind::IdentifierExpression: out << cast(*this).name(); break; + case ExpressionKind::DotSelfExpression: + out << ".Self"; + break; case ExpressionKind::IntLiteral: out << cast(*this).value(); break; diff --git a/explorer/ast/expression.h b/explorer/ast/expression.h index 573c2013dbd5..615a19d1305a 100644 --- a/explorer/ast/expression.h +++ b/explorer/ast/expression.h @@ -28,6 +28,7 @@ class MemberName; class VariableType; class InterfaceType; class ImplBinding; +class GenericBinding; class Expression : public AstNode { public: @@ -154,6 +155,40 @@ class IdentifierExpression : public Expression { std::optional value_node_; }; +// A `.Self` expression within either a `:!` binding or a standalone `where` +// expression. +// +// In a `:!` binding, the type of `.Self` is always `Type`. For example, in +// `A:! AddableWith(.Self)`, the expression `.Self` refers to the same type as +// `A`, but with type `Type`. +// +// In a `where` binding, the type of `.Self` is the constraint preceding the +// `where` keyword. For example, in `Foo where .Result is Bar(.Self)`, the type +// of `.Self` is `Foo`. +class DotSelfExpression : public Expression { + public: + explicit DotSelfExpression(SourceLocation source_loc) + : Expression(AstNodeKind::DotSelfExpression, source_loc) {} + + static auto classof(const AstNode* node) -> bool { + return InheritsFromDotSelfExpression(node->kind()); + } + + // The self binding. Cannot be called before name resolution. + auto self_binding() const -> const GenericBinding& { return **self_binding_; } + auto self_binding() -> GenericBinding& { return **self_binding_; } + + // Sets the self binding. Called only once, during name resolution. + void set_self_binding(Nonnull self_binding) { + CARBON_CHECK(!self_binding_.has_value()); + self_binding_ = self_binding; + } + + private: + std::string name_; + std::optional> self_binding_; +}; + class SimpleMemberAccessExpression : public Expression { public: explicit SimpleMemberAccessExpression(SourceLocation source_loc, @@ -741,20 +776,24 @@ class EqualsWhereClause : public WhereClause { }; // A `where` expression: `AddableWith(i32) where .Result == i32`. +// +// The first operand is rewritten to a generic binding, for example +// `.Self:! AddableWith(i32)`, which may be used in the clauses. class WhereExpression : public Expression { public: - explicit WhereExpression(SourceLocation source_loc, Nonnull base, + explicit WhereExpression(SourceLocation source_loc, + Nonnull self_binding, std::vector> clauses) : Expression(AstNodeKind::WhereExpression, source_loc), - base_(base), + self_binding_(self_binding), clauses_(std::move(clauses)) {} static auto classof(const AstNode* node) -> bool { return InheritsFromWhereExpression(node->kind()); } - auto base() const -> const Expression& { return *base_; } - auto base() -> Expression& { return *base_; } + auto self_binding() const -> const GenericBinding& { return *self_binding_; } + auto self_binding() -> GenericBinding& { return *self_binding_; } auto clauses() const -> llvm::ArrayRef> { return clauses_; @@ -762,7 +801,7 @@ class WhereExpression : public Expression { auto clauses() -> llvm::ArrayRef> { return clauses_; } private: - Nonnull base_; + Nonnull self_binding_; std::vector> clauses_; }; diff --git a/explorer/ast/pattern.h b/explorer/ast/pattern.h index 7ae38a6690ca..414f5a1f1a45 100644 --- a/explorer/ast/pattern.h +++ b/explorer/ast/pattern.h @@ -78,6 +78,12 @@ class Pattern : public AstNode { // and after typechecking it's guaranteed to be true. auto has_value() const -> bool { return value_.has_value(); } + // Determines whether the pattern has already been type-checked. Should + // only be used by type-checking. + auto is_type_checked() const -> bool { + return static_type_.has_value() && value_.has_value(); + } + protected: // Constructs a Pattern representing syntax at the given line number. // `kind` must be the enumerator corresponding to the most-derived type being @@ -277,12 +283,22 @@ class GenericBinding : public Pattern { // Set the original generic binding. void set_original(Nonnull orig) { original_ = orig; } + // Returns whether this binding has been named as a type within its own type + // expression via `.Self`. Set by type-checking. + auto named_as_type_via_dot_self() const -> bool { + return named_as_type_via_dot_self_; + } + // Set that this binding was named as a type within its own type expression + // via `.Self`. May only be called during type-checking. + void set_named_as_type_via_dot_self() { named_as_type_via_dot_self_ = true; } + private: std::string name_; Nonnull type_; std::optional> symbolic_identity_; std::optional> impl_binding_; std::optional> original_; + bool named_as_type_via_dot_self_ = false; }; // Converts paren_contents to a Pattern, interpreting the parentheses as diff --git a/explorer/ast/static_scope.h b/explorer/ast/static_scope.h index dd7777306f28..579cc3529144 100644 --- a/explorer/ast/static_scope.h +++ b/explorer/ast/static_scope.h @@ -163,7 +163,7 @@ class StaticScope { // Make `parent` a parent of this scope. // REQUIRES: `parent` is not already a parent of this scope. - void AddParent(Nonnull parent) { + void AddParent(Nonnull parent) { parent_scopes_.push_back(parent); } @@ -188,7 +188,7 @@ class StaticScope { std::unordered_map declared_names_; // A list of scopes used for name lookup within this scope. - std::vector> parent_scopes_; + std::vector> parent_scopes_; }; } // namespace Carbon diff --git a/explorer/fuzzing/ast_to_proto.cpp b/explorer/fuzzing/ast_to_proto.cpp index b428c2a95536..5c250c266351 100644 --- a/explorer/fuzzing/ast_to_proto.cpp +++ b/explorer/fuzzing/ast_to_proto.cpp @@ -13,6 +13,7 @@ namespace Carbon { using ::llvm::cast; +using ::llvm::isa; static auto ExpressionToProto(const Expression& expression) -> Fuzzing::Expression; @@ -108,6 +109,13 @@ static auto ExpressionToProto(const Expression& expression) case ExpressionKind::SimpleMemberAccessExpression: { const auto& simple_member_access = cast(expression); + if (isa(simple_member_access.object())) { + // The parser rewrites `.Foo` into `.Self.Foo`. Undo this + // transformation. + auto* designator_proto = expression_proto.mutable_designator(); + designator_proto->set_name(simple_member_access.member()); + break; + } auto* simple_member_access_proto = expression_proto.mutable_simple_member_access(); simple_member_access_proto->set_field(simple_member_access.member()); @@ -182,7 +190,8 @@ static auto ExpressionToProto(const Expression& expression) case ExpressionKind::WhereExpression: { const auto& where = cast(expression); auto* where_proto = expression_proto.mutable_where(); - *where_proto->mutable_base() = ExpressionToProto(where.base()); + *where_proto->mutable_base() = + ExpressionToProto(where.self_binding().type()); for (const WhereClause* where : where.clauses()) { Fuzzing::WhereClause clause_proto; switch (where->kind()) { @@ -208,6 +217,12 @@ static auto ExpressionToProto(const Expression& expression) break; } + case ExpressionKind::DotSelfExpression: { + auto* designator_proto = expression_proto.mutable_designator(); + designator_proto->set_name("Self"); + break; + } + case ExpressionKind::IntrinsicExpression: { const auto& intrinsic = cast(expression); auto* intrinsic_proto = expression_proto.mutable_intrinsic(); diff --git a/explorer/interpreter/BUILD b/explorer/interpreter/BUILD index 6a7d2d738b24..1b58053dc1b3 100644 --- a/explorer/interpreter/BUILD +++ b/explorer/interpreter/BUILD @@ -26,7 +26,6 @@ cc_library( "//common:ostream", "//explorer/ast:declaration", "//explorer/ast:expression", - "//explorer/ast:pattern", "//explorer/ast:statement", "//explorer/common:arena", "//explorer/common:error_builders", @@ -141,7 +140,6 @@ cc_library( "//explorer/ast", "//explorer/ast:declaration", "//explorer/ast:expression", - "//explorer/ast:pattern", "//explorer/common:arena", "//explorer/common:error_builders", "@llvm-project//llvm:Support", @@ -174,7 +172,6 @@ cc_library( "//explorer/ast", "//explorer/ast:declaration", "//explorer/ast:expression", - "//explorer/ast:pattern", "//explorer/ast:return_term", "//explorer/ast:statement", "//explorer/ast:static_scope", diff --git a/explorer/interpreter/interpreter.cpp b/explorer/interpreter/interpreter.cpp index 46a6e2ab736b..e9b1fbce3df4 100644 --- a/explorer/interpreter/interpreter.cpp +++ b/explorer/interpreter/interpreter.cpp @@ -408,6 +408,7 @@ auto Interpreter::StepLvalue() -> ErrorOr { case ExpressionKind::IntrinsicExpression: case ExpressionKind::IfExpression: case ExpressionKind::WhereExpression: + case ExpressionKind::DotSelfExpression: case ExpressionKind::ArrayTypeLiteral: case ExpressionKind::InstantiateImpl: CARBON_FATAL() << "Can't treat expression as lvalue: " << exp; @@ -927,6 +928,14 @@ auto Interpreter::StepExp() -> ErrorOr { } return todo_.FinishAction(value); } + case ExpressionKind::DotSelfExpression: { + // `.Self` always symbolically resolves to the self binding, even if it's + // not yet been type-checked. + CARBON_CHECK(act.pos() == 0); + const auto& dot_self = cast(exp); + return todo_.FinishAction( + arena_->New(&dot_self.self_binding())); + } case ExpressionKind::IntLiteral: CARBON_CHECK(act.pos() == 0); // { {n :: C, E, F} :: S, H} -> { {n' :: C, E, F} :: S, H} diff --git a/explorer/interpreter/resolve_names.cpp b/explorer/interpreter/resolve_names.cpp index 6173cad34024..e0f4a0babc28 100644 --- a/explorer/interpreter/resolve_names.cpp +++ b/explorer/interpreter/resolve_names.cpp @@ -166,6 +166,15 @@ static auto ResolveNames(Expression& expression, identifier.set_value_node(value_node); break; } + case ExpressionKind::DotSelfExpression: { + auto& dot_self = cast(expression); + CARBON_ASSIGN_OR_RETURN( + const auto value_node, + enclosing_scope.Resolve(".Self", dot_self.source_loc())); + dot_self.set_self_binding(const_cast( + &cast(value_node.base()))); + break; + } case ExpressionKind::IntrinsicExpression: CARBON_RETURN_IF_ERROR(ResolveNames( cast(expression).args(), enclosing_scope)); @@ -182,13 +191,14 @@ static auto ResolveNames(Expression& expression, } case ExpressionKind::WhereExpression: { auto& where = cast(expression); - // TODO: Introduce `.Self` into scope? - // StaticScope where_scope; - // where_scope.AddParent(&enclosing_scope); - // where_scope.Add(".Self", ???); - CARBON_RETURN_IF_ERROR(ResolveNames(where.base(), enclosing_scope)); + CARBON_RETURN_IF_ERROR( + ResolveNames(where.self_binding().type(), enclosing_scope)); + // Introduce `.Self` into scope on the right of the `where` keyword. + StaticScope where_scope; + where_scope.AddParent(&enclosing_scope); + CARBON_RETURN_IF_ERROR(where_scope.Add(".Self", &where.self_binding())); for (Nonnull clause : where.clauses()) { - CARBON_RETURN_IF_ERROR(ResolveNames(*clause, enclosing_scope)); + CARBON_RETURN_IF_ERROR(ResolveNames(*clause, where_scope)); } break; } @@ -253,7 +263,11 @@ static auto ResolveNames(Pattern& pattern, StaticScope& enclosing_scope) } case PatternKind::GenericBinding: { auto& binding = cast(pattern); - CARBON_RETURN_IF_ERROR(ResolveNames(binding.type(), enclosing_scope)); + // `.Self` is in scope in the context of the type. + StaticScope self_scope; + self_scope.AddParent(&enclosing_scope); + CARBON_RETURN_IF_ERROR(self_scope.Add(".Self", &binding)); + CARBON_RETURN_IF_ERROR(ResolveNames(binding.type(), self_scope)); if (binding.name() != AnonymousName) { CARBON_RETURN_IF_ERROR(enclosing_scope.Add(binding.name(), &binding)); } @@ -425,8 +439,7 @@ static auto ResolveNames(Declaration& declaration, StaticScope& enclosing_scope) StaticScope function_scope; function_scope.AddParent(&enclosing_scope); for (Nonnull binding : function.deduced_parameters()) { - CARBON_RETURN_IF_ERROR(ResolveNames(binding->type(), function_scope)); - CARBON_RETURN_IF_ERROR(function_scope.Add(binding->name(), binding)); + CARBON_RETURN_IF_ERROR(ResolveNames(*binding, function_scope)); } if (function.is_method()) { CARBON_RETURN_IF_ERROR( diff --git a/explorer/interpreter/type_checker.cpp b/explorer/interpreter/type_checker.cpp index 931027e8ae7a..7f055708424b 100644 --- a/explorer/interpreter/type_checker.cpp +++ b/explorer/interpreter/type_checker.cpp @@ -757,11 +757,6 @@ class ConstraintTypeBuilder { source_loc, ".Self", arena->New(source_loc))) {} ConstraintTypeBuilder(Nonnull self_binding) : self_binding_(self_binding) {} - ConstraintTypeBuilder(Nonnull constraint) - : self_binding_(constraint->self_binding()), - impl_constraints_(constraint->impl_constraints()), - equality_constraints_(constraint->equality_constraints()), - lookup_contexts_(constraint->lookup_contexts()) {} // Produce a type that refers to the `.Self` type of the constraint. auto MakeSelfType(Nonnull arena) const @@ -1090,8 +1085,6 @@ auto TypeChecker::MatchImpl(const InterfaceType& iface, return std::nullopt; } - // TODO: If the `interface` is a ConstraintType, for every impl_constraint, - // try deduction against that. if (ErrorOr e = ArgumentDeduction( source_loc, "match", impl.deduced, deduced_args, impl.interface, &iface, /*allow_implicit_conversion=*/false, impl_scope); @@ -1259,6 +1252,8 @@ auto TypeChecker::DeduceCallBindings( impl_bindings, impl_scope, call.source_loc(), generic_bindings, impls)); call.set_impls(impls); + // TODO: Ensure any equality constraints are satisfied. + // Convert the arguments to the parameter type. Nonnull param_type = Substitute(generic_bindings, params_type); CARBON_ASSIGN_OR_RETURN( @@ -1778,6 +1773,17 @@ auto TypeChecker::TypeCheckExp(Nonnull e, ident.set_value_category(ident.value_node().value_category()); return Success(); } + case ExpressionKind::DotSelfExpression: { + auto& dot_self = cast(*e); + if (dot_self.self_binding().is_type_checked()) { + dot_self.set_static_type(&dot_self.self_binding().static_type()); + } else { + dot_self.set_static_type(arena_->New()); + dot_self.self_binding().set_named_as_type_via_dot_self(); + } + dot_self.set_value_category(ValueCategory::Let); + return Success(); + } case ExpressionKind::IntLiteral: e->set_value_category(ValueCategory::Let); e->set_static_type(arena_->New()); @@ -2074,28 +2080,38 @@ auto TypeChecker::TypeCheckExp(Nonnull e, } case ExpressionKind::WhereExpression: { auto& where = cast(*e); - CARBON_RETURN_IF_ERROR(TypeCheckExp(&where.base(), impl_scope)); + ImplScope inner_impl_scope; + inner_impl_scope.AddParent(&impl_scope); + CARBON_RETURN_IF_ERROR(TypeCheckPattern(&where.self_binding(), + std::nullopt, inner_impl_scope, + ValueCategory::Let)); for (Nonnull clause : where.clauses()) { - CARBON_RETURN_IF_ERROR(TypeCheckWhereClause(clause, impl_scope)); + CARBON_RETURN_IF_ERROR(TypeCheckWhereClause(clause, inner_impl_scope)); } - const ConstraintType* base; - const Value& base_type = where.base().static_type(); - if (auto* constraint_type_type = - dyn_cast(&base_type)) { - base = &constraint_type_type->constraint_type(); - } else if (auto* interface_type_type = - dyn_cast(&base_type)) { - base = MakeConstraintForInterface( - e->source_loc(), &interface_type_type->interface_type()); + std::optional> base; + const Value& base_type = where.self_binding().static_type(); + if (auto* constraint_type = dyn_cast(&base_type)) { + base = constraint_type; + } else if (auto* interface_type = dyn_cast(&base_type)) { + base = MakeConstraintForInterface(e->source_loc(), interface_type); + } else if (isa(base_type)) { + // Start with an unconstrained type. } else { return CompilationError(e->source_loc()) << "expected constraint as first operand of `where` expression, " << "found " << base_type; } + // Start with the given constraint, if any. + ConstraintTypeBuilder builder(&where.self_binding()); + if (base) { + BindingMap map; + map[(*base)->self_binding()] = builder.MakeSelfType(arena_); + builder.Add(cast(Substitute(map, *base))); + } + // Apply the `where` clauses. - ConstraintTypeBuilder builder(base); for (Nonnull clause : where.clauses()) { switch (clause->kind()) { case WhereClauseKind::IsWhereClause: { @@ -2340,6 +2356,11 @@ auto TypeChecker::TypeCheckPattern( << binding; } binding.set_static_type(type); + if (binding.named_as_type_via_dot_self() && !IsTypeOfType(type)) { + return CompilationError(binding.type().source_loc()) + << "`.Self` used in type of non-type binding `" << binding.name() + << "`"; + } CARBON_ASSIGN_OR_RETURN(Nonnull val, InterpPattern(&binding, arena_, trace_stream_)); binding.set_symbolic_identity(val); diff --git a/explorer/syntax/BUILD b/explorer/syntax/BUILD index f65aa3c56542..4c868ff0fefe 100644 --- a/explorer/syntax/BUILD +++ b/explorer/syntax/BUILD @@ -81,7 +81,6 @@ cc_library( "//explorer/ast:declaration", "//explorer/ast:expression", "//explorer/ast:paren_contents", - "//explorer/ast:pattern", "//explorer/ast:value_category", "//explorer/common:arena", "//explorer/common:error_builders", diff --git a/explorer/syntax/parser.ypp b/explorer/syntax/parser.ypp index 83c49779b729..ecb73c62c27c 100644 --- a/explorer/syntax/parser.ypp +++ b/explorer/syntax/parser.ypp @@ -301,6 +301,15 @@ api_or_impl: primary_expression: identifier { $$ = arena->New(context.source_loc(), $1); } +| designator + { + // `.Foo` is rewritten to `.Self.Foo`. + $$ = arena->New( + context.source_loc(), + arena->New(context.source_loc()), $1); + } +| PERIOD SELF + { $$ = arena->New(context.source_loc()); } | integer_literal { $$ = arena->New(context.source_loc(), $1); } | string_literal @@ -544,7 +553,11 @@ where_clause_list: ; where_expression: type_expression WHERE where_clause_list - { $$ = arena->New(context.source_loc(), $1, $3); } + { + auto* self = + arena -> New(context.source_loc(), ".Self", $1); + $$ = arena->New(context.source_loc(), self, $3); + } ; statement_expression: ref_deref_expression diff --git a/explorer/testdata/constraint/binding_self.carbon b/explorer/testdata/constraint/binding_self.carbon new file mode 100644 index 000000000000..3ef0e6c2040c --- /dev/null +++ b/explorer/testdata/constraint/binding_self.carbon @@ -0,0 +1,30 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// RUN: %{explorer} %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s +// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: %{explorer} %s +// CHECK: result: 12 + +package ExplorerTest api; + +interface SwizzleWith(T:! Type) { + fn Op[me: Self](x: T) -> i32; +} + +impl i32 as SwizzleWith(i32) { + fn Op[me: Self](x: Self) -> Self { return me * 10 + x; } +} + +fn F[T:! SwizzleWith(.Self)](v: T, w: T) -> i32 { + return v.Op(w); +} + +fn Main() -> i32 { + var one: i32 = 1; + var two: i32 = 2; + return F(one, two); +} diff --git a/explorer/testdata/constraint/combine_equality.carbon b/explorer/testdata/constraint/combine_equality.carbon index ea2ee7ce8d52..67aca89c7651 100644 --- a/explorer/testdata/constraint/combine_equality.carbon +++ b/explorer/testdata/constraint/combine_equality.carbon @@ -15,7 +15,7 @@ impl i32 as I {} fn F(A:! i32, B:! i32, C:! i32, D:! i32, E:! i32, T:! I where A == B and C == D and C == E and B == D) { - // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/constraint/combine_equality.carbon:[[@LINE+1]]: member access, F not in constraint interface I where .Self:! Type is interface I, A:! i32 == B:! i32 == E:! i32 == C:! i32 == D:! i32 + // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/constraint/combine_equality.carbon:[[@LINE+1]]: member access, F not in constraint interface I where .Self:! I is interface I, A:! i32 == B:! i32 == E:! i32 == C:! i32 == D:! i32 T.F(); } diff --git a/explorer/testdata/constraint/dot_self_is_other.carbon b/explorer/testdata/constraint/dot_self_is_other.carbon new file mode 100644 index 000000000000..6bee40ffa8a7 --- /dev/null +++ b/explorer/testdata/constraint/dot_self_is_other.carbon @@ -0,0 +1,31 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// RUN: %{explorer} %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s +// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: %{explorer} %s +// CHECK: result: 12 + +package ExplorerTest api; + +interface A { fn F() -> i32; } +interface B { fn F() -> i32; } + +impl i32 as A { fn F() -> i32 { return 1; } } +impl i32 as B { fn F() -> i32 { return 2; } } + +fn GetA[T:! A](x: T) -> i32 { return x.F(); } +fn GetAB[T:! B where .Self is A](x: T) -> i32 { + // Note that there's no ambiguity in the use of the name F here. + // `x.F` is `x.(B.F)` because the constraint only treats `B`, not + // `A`, as a lookup context. + return 10 * GetA(x) + x.F(); +} + +fn Main() -> i32 { + var v: i32 = 0; + return GetAB(v); +} diff --git a/explorer/testdata/constraint/fail_dot_self_after_scope.carbon b/explorer/testdata/constraint/fail_dot_self_after_scope.carbon new file mode 100644 index 000000000000..dcf15c7db576 --- /dev/null +++ b/explorer/testdata/constraint/fail_dot_self_after_scope.carbon @@ -0,0 +1,22 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// RUN: %{not} %{explorer} %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s +// RUN: %{not} %{explorer} --parser_debug --trace_file=- %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: %{explorer} %s + +package ExplorerTest api; + +interface FrobWith(T:! Type) {} + +fn F[T:! FrobWith(.Self)] + // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/constraint/fail_dot_self_after_scope.carbon:[[@LINE+1]]: could not resolve '.Self' + (U: .Self) { +} + +fn Main() -> i32 { + return 0; +} diff --git a/explorer/testdata/constraint/fail_dot_self_after_scope_2.carbon b/explorer/testdata/constraint/fail_dot_self_after_scope_2.carbon new file mode 100644 index 000000000000..c586f9609f70 --- /dev/null +++ b/explorer/testdata/constraint/fail_dot_self_after_scope_2.carbon @@ -0,0 +1,22 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// RUN: %{not} %{explorer} %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s +// RUN: %{not} %{explorer} --parser_debug --trace_file=- %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: %{explorer} %s + +package ExplorerTest api; + +interface FrobWith(T:! Type) {} + +fn F[T:! FrobWith(.Self), + // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/constraint/fail_dot_self_after_scope_2.carbon:[[@LINE+1]]: `.Self` used in type of non-type binding `U` + U:! .Self]() { +} + +fn Main() -> i32 { + return 0; +} diff --git a/explorer/testdata/constraint/fail_dot_self_not_in_scope.carbon b/explorer/testdata/constraint/fail_dot_self_not_in_scope.carbon new file mode 100644 index 000000000000..d7a9684ad702 --- /dev/null +++ b/explorer/testdata/constraint/fail_dot_self_not_in_scope.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 +// +// 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; + +// CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/constraint/fail_dot_self_not_in_scope.carbon:[[@LINE+1]]: could not resolve '.Self' +fn Main() -> .Self { + return 0; +} diff --git a/explorer/testdata/constraint/fail_infinite_self.carbon b/explorer/testdata/constraint/fail_infinite_self.carbon new file mode 100644 index 000000000000..d9de04374912 --- /dev/null +++ b/explorer/testdata/constraint/fail_infinite_self.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 +// +// 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; + +// CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/constraint/fail_infinite_self.carbon:[[@LINE+1]]: `.Self` used in type of non-type binding `T` +fn F[T:! .Self]() {} + +fn Main() -> i32 { return 0; } diff --git a/explorer/testdata/constraint/fail_infinite_self_ptr.carbon b/explorer/testdata/constraint/fail_infinite_self_ptr.carbon new file mode 100644 index 000000000000..efa75f1d6a84 --- /dev/null +++ b/explorer/testdata/constraint/fail_infinite_self_ptr.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 +// +// 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; + +// CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/constraint/fail_infinite_self_ptr.carbon:[[@LINE+1]]: `.Self` used in type of non-type binding `T` +fn F[T:! .Self*]() {} + +fn Main() -> i32 { return 0; } diff --git a/explorer/testdata/constraint/fail_nontype_self.carbon b/explorer/testdata/constraint/fail_nontype_self.carbon new file mode 100644 index 000000000000..240a73c109f7 --- /dev/null +++ b/explorer/testdata/constraint/fail_nontype_self.carbon @@ -0,0 +1,18 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// RUN: %{not} %{explorer} %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s +// RUN: %{not} %{explorer} --parser_debug --trace_file=- %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: %{explorer} %s + +package ExplorerTest api; + +class X(T:! Type) {} + +// CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/constraint/fail_nontype_self.carbon:[[@LINE+1]]: `.Self` used in type of non-type binding `T` +fn F[T:! X(.Self)](x: T) {} + +fn Main() -> i32 { return 0; } diff --git a/explorer/testdata/constraint/qualified_lookup_in_where.carbon b/explorer/testdata/constraint/qualified_lookup_in_where.carbon new file mode 100644 index 000000000000..04460eaa7cf6 --- /dev/null +++ b/explorer/testdata/constraint/qualified_lookup_in_where.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: %{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: 122 + +package ExplorerTest api; + +interface A { fn F() -> i32; } +interface B { fn F() -> i32; } + +impl i32 as A { fn F() -> i32 { return 1; } } +impl i32 as B { fn F() -> i32 { return 2; } } + +fn GetAB(T:! B where .Self is A) -> i32 { + return 100 * T.(A.F)() + 10 * T.(B.F)() + T.F(); +} + +fn Main() -> i32 { + return GetAB(i32); +} diff --git a/explorer/testdata/constraint/where_self.carbon b/explorer/testdata/constraint/where_self.carbon new file mode 100644 index 000000000000..3abf0d78ef6c --- /dev/null +++ b/explorer/testdata/constraint/where_self.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: 12 + +package ExplorerTest api; + +interface SwizzleWith(T:! Type) { + fn Op[me: Self](x: T) -> i32; +} + +// TODO: There should be some way to write this that includes +// `SwizzleWith(.Self)` in the list of lookup contexts. +alias Swizzle = Type where .Self is SwizzleWith(.Self); + +impl i32 as SwizzleWith(i32) { + fn Op[me: Self](x: Self) -> Self { return me * 10 + x; } +} + +fn F[T:! Swizzle](v: T, w: T) -> i32 { + return v.(SwizzleWith(T).Op)(w); +} + +fn Main() -> i32 { + var one: i32 = 1; + var two: i32 = 2; + return F(one, two); +}