From e0c90767be5a0ede5f4ee55bf7036acfba2ba7d5 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Wed, 22 Mar 2023 14:19:02 -0700 Subject: [PATCH] Support for templated impl declarations (#2700) The strategy that we use for now to support template instantiation is to check the impl declaration as if it were a generic, but to defer all checking of the impl definition until we see a use in which all template parameters have arguments. At that point, we clone the impl definition and type-check the whole thing, with constant values set on the template parameters corresponding to the given arguments. No caching of template instantiations is performed yet; each time we form a reference to a template instantiation, we instantiate it afresh. We also don't implement the name lookup rule from #949 yet; lookups during template instantiation look only in the actual type and not in the constraint. Depends on #2699 --- common/error.h | 17 +- common/fuzzing/carbon.proto | 5 + common/fuzzing/proto_to_carbon.cpp | 7 + explorer/ast/declaration.h | 3 +- explorer/ast/pattern.cpp | 12 + explorer/ast/pattern.h | 28 ++- explorer/data/prelude.carbon | 2 +- explorer/fuzzing/BUILD | 22 -- explorer/fuzzing/ast_to_proto.cpp | 8 + explorer/interpreter/interpreter.cpp | 3 +- explorer/interpreter/type_checker.cpp | 216 ++++++++++++++++-- explorer/interpreter/type_checker.h | 25 +- explorer/syntax/lexer.lpp | 2 + explorer/syntax/parser.ypp | 26 ++- .../assoc_const/fail_anonymous.carbon | 2 +- .../testdata/template/fail_name_lookup.carbon | 46 ++++ .../testdata/template/fail_no_member.carbon | 22 ++ .../testdata/template/member_access.carbon | 32 +++ explorer/testdata/template/name_lookup.carbon | 77 +++++++ 19 files changed, 491 insertions(+), 64 deletions(-) create mode 100644 explorer/testdata/template/fail_name_lookup.carbon create mode 100644 explorer/testdata/template/fail_no_member.carbon create mode 100644 explorer/testdata/template/member_access.carbon create mode 100644 explorer/testdata/template/name_lookup.carbon diff --git a/common/error.h b/common/error.h index 574c46643623..d4b5d0578795 100644 --- a/common/error.h +++ b/common/error.h @@ -36,7 +36,7 @@ class [[nodiscard]] Error { : location_(std::move(other.location_)), message_(std::move(other.message_)) {} - Error& operator=(Error&& other) noexcept { + auto operator=(Error&& other) noexcept -> Error& { location_ = std::move(other.location_); message_ = std::move(other.message_); return *this; @@ -122,7 +122,7 @@ class [[nodiscard]] ErrorOr { } private: - // Either an error message or + // Either an error message or a value. std::variant val_; }; @@ -171,15 +171,20 @@ class ErrorBuilder { #define CARBON_MAKE_UNIQUE_NAME_IMPL(a, b, c) a##b##c #define CARBON_MAKE_UNIQUE_NAME(a, b, c) CARBON_MAKE_UNIQUE_NAME_IMPL(a, b, c) +// Macro to prevent a top-level comma from being interpreted as a macro +// argument separator. +#define CARBON_PROTECT_COMMAS(...) __VA_ARGS__ + #define CARBON_RETURN_IF_ERROR_IMPL(unique_name, expr) \ if (auto unique_name = (expr); /* NOLINT(bugprone-macro-parentheses) */ \ !(unique_name).ok()) { \ return std::move(unique_name).error(); \ } -#define CARBON_RETURN_IF_ERROR(expr) \ - CARBON_RETURN_IF_ERROR_IMPL( \ - CARBON_MAKE_UNIQUE_NAME(_llvm_error_line, __LINE__, __COUNTER__), expr) +#define CARBON_RETURN_IF_ERROR(expr) \ + CARBON_RETURN_IF_ERROR_IMPL( \ + CARBON_MAKE_UNIQUE_NAME(_llvm_error_line, __LINE__, __COUNTER__), \ + CARBON_PROTECT_COMMAS(expr)) #define CARBON_ASSIGN_OR_RETURN_IMPL(unique_name, var, expr) \ auto unique_name = (expr); /* NOLINT(bugprone-macro-parentheses) */ \ @@ -191,6 +196,6 @@ class ErrorBuilder { #define CARBON_ASSIGN_OR_RETURN(var, expr) \ CARBON_ASSIGN_OR_RETURN_IMPL( \ CARBON_MAKE_UNIQUE_NAME(_llvm_expected_line, __LINE__, __COUNTER__), \ - var, expr) + CARBON_PROTECT_COMMAS(var), CARBON_PROTECT_COMMAS(expr)) #endif // CARBON_COMMON_ERROR_H_ diff --git a/common/fuzzing/carbon.proto b/common/fuzzing/carbon.proto index 6025b7990b53..65bf5f5b88c1 100644 --- a/common/fuzzing/carbon.proto +++ b/common/fuzzing/carbon.proto @@ -198,8 +198,13 @@ message BindingPattern { } message GenericBinding { + enum Kind { + Checked = 0; + Template = 1; + } optional string name = 1; optional Expression type = 2; + optional Kind kind = 3; } message TuplePattern { diff --git a/common/fuzzing/proto_to_carbon.cpp b/common/fuzzing/proto_to_carbon.cpp index da63c6475dc9..27b476ae1bd5 100644 --- a/common/fuzzing/proto_to_carbon.cpp +++ b/common/fuzzing/proto_to_carbon.cpp @@ -433,6 +433,13 @@ static auto BindingPatternToCarbon(const Fuzzing::BindingPattern& pattern, static auto GenericBindingToCarbon( const Fuzzing::GenericBinding& generic_binding, llvm::raw_ostream& out) { + switch (generic_binding.kind()) { + case Fuzzing::GenericBinding::Checked: + break; + case Fuzzing::GenericBinding::Template: + out << "template "; + break; + } IdentifierToCarbon(generic_binding.name(), out); out << ":! "; ExpressionToCarbon(generic_binding.type(), out); diff --git a/explorer/ast/declaration.h b/explorer/ast/declaration.h index a5328c489aca..81641bda12af 100644 --- a/explorer/ast/declaration.h +++ b/explorer/ast/declaration.h @@ -674,7 +674,8 @@ class ConstraintTypeDeclaration : public Declaration { auto* self_type_ref = arena->New( source_loc, std::string(name_.inner_name())); self_type_ref->set_value_node(self_type_); - self_ = arena->New(source_loc, "Self", self_type_ref); + self_ = arena->New(source_loc, "Self", self_type_ref, + GenericBinding::BindingKind::Checked); } explicit ConstraintTypeDeclaration(CloneContext& context, diff --git a/explorer/ast/pattern.cpp b/explorer/ast/pattern.cpp index f5780adc7036..8b36d3ee5af2 100644 --- a/explorer/ast/pattern.cpp +++ b/explorer/ast/pattern.cpp @@ -33,7 +33,17 @@ void Pattern::Print(llvm::raw_ostream& out) const { } case PatternKind::GenericBinding: { const auto& binding = cast(*this); + switch (binding.binding_kind()) { + case GenericBinding::BindingKind::Checked: + break; + case GenericBinding::BindingKind::Template: + out << "template "; + break; + } out << binding.name() << ":! " << binding.type(); + if (auto value = binding.constant_value()) { + out << " = " << **value; + } break; } case PatternKind::TuplePattern: { @@ -177,6 +187,8 @@ GenericBinding::GenericBinding(CloneContext& context, : Pattern(context, other), name_(other.name_), type_(context.Clone(other.type_)), + binding_kind_(other.binding_kind_), + template_value_(context.Clone(other.template_value_)), symbolic_identity_(context.Clone(other.symbolic_identity_)), impl_binding_(context.Clone(other.impl_binding_)), original_(context.Remap(other.original_)), diff --git a/explorer/ast/pattern.h b/explorer/ast/pattern.h index 60b75bd7030f..0a9e26bc989e 100644 --- a/explorer/ast/pattern.h +++ b/explorer/ast/pattern.h @@ -270,11 +270,19 @@ class GenericBinding : public Pattern { public: using ImplementsCarbonValueNode = void; - GenericBinding(SourceLocation source_loc, std::string name, - Nonnull type) + enum class BindingKind { + // A checked generic binding, `T:! type`. + Checked, + // A template generic binding, `template T:! type`. + Template, + }; + + explicit GenericBinding(SourceLocation source_loc, std::string name, + Nonnull type, BindingKind binding_kind) : Pattern(AstNodeKind::GenericBinding, source_loc), name_(std::move(name)), - type_(type) {} + type_(type), + binding_kind_(binding_kind) {} explicit GenericBinding(CloneContext& context, const GenericBinding& other); @@ -285,6 +293,7 @@ class GenericBinding : public Pattern { auto name() const -> const std::string& { return name_; } auto type() const -> const Expression& { return *type_; } auto type() -> Expression& { return *type_; } + auto binding_kind() const -> BindingKind { return binding_kind_; } // The index of this binding, which is the number of bindings that are in // scope at the point where this binding is declared. @@ -299,7 +308,7 @@ class GenericBinding : public Pattern { auto value_category() const -> ValueCategory { return ValueCategory::Let; } auto constant_value() const -> std::optional> { - return std::nullopt; + return template_value_; } auto symbolic_identity() const -> std::optional> { @@ -310,6 +319,15 @@ class GenericBinding : public Pattern { symbolic_identity_ = value; } + void set_template_value(Nonnull template_value) { + CARBON_CHECK(binding_kind_ == BindingKind::Template); + template_value_ = template_value; + } + auto has_template_value() const -> bool { + CARBON_CHECK(binding_kind_ == BindingKind::Template); + return template_value_.has_value(); + } + // The impl binding associated with this type variable. auto impl_binding() const -> std::optional> { return impl_binding_; @@ -343,7 +361,9 @@ class GenericBinding : public Pattern { private: std::string name_; Nonnull type_; + BindingKind binding_kind_; std::optional index_; + std::optional> template_value_; std::optional> symbolic_identity_; std::optional> impl_binding_; std::optional> original_; diff --git a/explorer/data/prelude.carbon b/explorer/data/prelude.carbon index e0e32eeff5ef..62a9941ed736 100644 --- a/explorer/data/prelude.carbon +++ b/explorer/data/prelude.carbon @@ -34,7 +34,7 @@ __match_first { // Pick up implicit conversions that are built into the compiler. // TODO: Split these into individual categories and implement as many as we can // in the prelude. - impl forall [U:! type, T:! __intrinsic_implicit_as(U)] T as ImplicitAs(U) { + impl forall [U:! type, template T:! __intrinsic_implicit_as(U)] T as ImplicitAs(U) { fn Convert[self: Self]() -> U { return __intrinsic_implicit_as_convert(self, U); } } diff --git a/explorer/fuzzing/BUILD b/explorer/fuzzing/BUILD index 8feef0cfe675..39f9642a599e 100644 --- a/explorer/fuzzing/BUILD +++ b/explorer/fuzzing/BUILD @@ -116,28 +116,6 @@ cc_test( ], ) -cc_test( - name = "clone_test", - srcs = ["clone_test.cpp"], - args = [ - "$(locations //explorer:standard_libraries)", - "$(locations //explorer/testdata:carbon_files)", - ], - data = [ - "//explorer:standard_libraries", - "//explorer/testdata:carbon_files", - ], - deps = [ - ":ast_to_proto_lib", - "//common/fuzzing:carbon_cc_proto", - "//explorer/ast", - "//explorer/syntax", - "@com_google_googletest//:gtest", - "@com_google_protobuf//:protobuf_headers", - "@llvm-project//llvm:Support", - ], -) - cc_fuzz_test( name = "explorer_fuzzer", testonly = 1, diff --git a/explorer/fuzzing/ast_to_proto.cpp b/explorer/fuzzing/ast_to_proto.cpp index 93e596336229..b62c46e4ee7c 100644 --- a/explorer/fuzzing/ast_to_proto.cpp +++ b/explorer/fuzzing/ast_to_proto.cpp @@ -379,6 +379,14 @@ static auto GenericBindingToProto(const GenericBinding& binding) Fuzzing::GenericBinding binding_proto; binding_proto.set_name(binding.name()); *binding_proto.mutable_type() = ExpressionToProto(binding.type()); + switch (binding.binding_kind()) { + case GenericBinding::BindingKind::Checked: + binding_proto.set_kind(Fuzzing::GenericBinding::Checked); + break; + case GenericBinding::BindingKind::Template: + binding_proto.set_kind(Fuzzing::GenericBinding::Template); + break; + } return binding_proto; } diff --git a/explorer/interpreter/interpreter.cpp b/explorer/interpreter/interpreter.cpp index 19d0e377480f..e697ef8204f2 100644 --- a/explorer/interpreter/interpreter.cpp +++ b/explorer/interpreter/interpreter.cpp @@ -1506,7 +1506,8 @@ auto Interpreter::StepExp() -> ErrorOr { // it from here. auto* self_binding = arena_->New( exp.source_loc(), ".Self", - arena_->New(exp.source_loc())); + arena_->New(exp.source_loc()), + GenericBinding::BindingKind::Checked); auto* self = arena_->New(self_binding); auto* impl_binding = arena_->New( exp.source_loc(), self_binding, std::nullopt); diff --git a/explorer/interpreter/type_checker.cpp b/explorer/interpreter/type_checker.cpp index 64a4fa58cf48..09cd24e6545c 100644 --- a/explorer/interpreter/type_checker.cpp +++ b/explorer/interpreter/type_checker.cpp @@ -392,6 +392,56 @@ static auto IsConcreteType(Nonnull value) -> bool { return IsType(value) && !TypeContainsAuto(value); } +// Returns whether the given value is template-dependent, that is, if it +// depends on any template paramaeter. +static auto IsTemplateDependent(Nonnull value) -> bool { + // A VariableType is template dependent if it names a template binding. + if (auto* var_type = dyn_cast(value)) { + return var_type->binding().binding_kind() == + GenericBinding::BindingKind::Template; + } + + static constexpr auto is_dependent_value = [](auto&& x) -> bool { + if constexpr (std::is_convertible_v) { + return IsTemplateDependent(x); + } + return false; + }; + + // Any other value is template dependent if any part of it is. + return value->Visit([](auto* derived_value) { + return derived_value->Decompose([](auto&&... parts) { + return (is_dependent_value(decltype(parts)(parts)) || ...); + }); + }); +} + +// Returns whether all template parameters in `bindings` are saturated: that +// is, they have arguments that are not dependent on any template parameter. +// This indicates that we're ready to perform template instantiation. +static auto IsTemplateSaturated(const Bindings& bindings) -> bool { + for (auto [binding, value] : bindings.args()) { + if (binding->binding_kind() == GenericBinding::BindingKind::Template && + IsTemplateDependent(value)) { + return false; + } + } + return true; +} + +// Returns whether all template parameters in `params` are saturated: that they +// have template argument values specified. +static auto IsTemplateSaturated( + llvm::ArrayRef> bindings) -> bool { + for (auto* binding : bindings) { + if (binding->binding_kind() == GenericBinding::BindingKind::Template && + !binding->has_template_value()) { + return false; + } + } + return true; +} + // Returns the named field, or None if not found. static auto FindField(llvm::ArrayRef fields, const std::string& field_name) @@ -1724,7 +1774,8 @@ class TypeChecker::ConstraintTypeBuilder { // Note, the type-of-type here is a placeholder and isn't really // meaningful. auto* result = arena->New( - source_loc, ".Self", arena->New(source_loc)); + source_loc, ".Self", arena->New(source_loc), + GenericBinding::BindingKind::Checked); PrepareSelfBinding(arena, result); return result; } @@ -1945,7 +1996,8 @@ class TypeChecker::SubstitutedGenericBindings { Nonnull new_binding = type_checker_->arena_->New( old_binding->source_loc(), old_binding->name(), - const_cast(&old_binding->type())); + const_cast(&old_binding->type()), + old_binding->binding_kind()); new_binding->set_original(old_binding->original()); new_binding->set_static_type(new_type); bindings_.Add(old_binding, @@ -2042,6 +2094,24 @@ class TypeChecker::SubstituteTransform } } + // When substituting into the bindings of an `ImplWitness`, we may need to + // perform template instantiation. + auto operator()(Nonnull witness) + -> ErrorOr> { + CARBON_ASSIGN_OR_RETURN(const auto* bindings, + Transform(&witness->bindings())); + const auto* declaration = &witness->declaration(); + if (!IsTemplateSaturated(witness->bindings()) && + IsTemplateSaturated(*bindings)) { + CARBON_ASSIGN_OR_RETURN( + CARBON_PROTECT_COMMAS(auto [new_decl, new_bindings]), + type_checker_->InstantiateImplDeclaration(declaration, bindings)); + declaration = new_decl; + bindings = new_bindings; + } + return type_checker_->arena_->New(declaration, bindings); + } + // For an associated constant, look for a rewrite. auto operator()(Nonnull assoc) -> ErrorOr> { @@ -5505,11 +5575,23 @@ auto TypeChecker::CheckAndAddImplBindings( } auto TypeChecker::DeclareImplDeclaration(Nonnull impl_decl, - const ScopeInfo& scope_info) + const ScopeInfo& scope_info, + bool is_template_instantiation) -> ErrorOr { if (trace_stream_->is_enabled()) { *trace_stream_ << "declaring " << *impl_decl << "\n"; } + + if (!IsTemplateSaturated(impl_decl->deduced_parameters())) { + CloneContext context(arena_); + TemplateInfo template_info = {.pattern = context.Clone(impl_decl)}; + for (auto deduced : impl_decl->deduced_parameters()) { + template_info.param_map.insert( + {deduced, context.GetExistingClone(deduced)}); + } + templates_.insert({impl_decl, std::move(template_info)}); + } + ImplScope impl_scope(scope_info.innermost_scope); std::vector> generic_bindings = scope_info.bindings; @@ -5549,8 +5631,9 @@ auto TypeChecker::DeclareImplDeclaration(Nonnull impl_decl, Nonnull constraint_type; { // TODO: Combine this with the SelfDeclaration. - auto* self_binding = arena_->New(self->source_loc(), "Self", - impl_decl->impl_type()); + auto* self_binding = arena_->New( + self->source_loc(), "Self", impl_decl->impl_type(), + GenericBinding::BindingKind::Checked); self_binding->set_symbolic_identity(impl_type_value); self_binding->set_value(impl_type_value); auto* impl_binding = arena_->New(self_binding->source_loc(), @@ -5578,14 +5661,24 @@ auto TypeChecker::DeclareImplDeclaration(Nonnull impl_decl, impl_decl->set_constraint_type(constraint_type); } + // Declare the impl members. An `impl` behaves like a class scope. + ScopeInfo impl_scope_info = + ScopeInfo::ForClassScope(scope_info, &impl_scope, generic_bindings); + for (Nonnull m : impl_decl->members()) { + CARBON_RETURN_IF_ERROR(DeclareDeclaration(m, impl_scope_info)); + } + // Build the self witness. This is the witness used to demonstrate that // this impl implements its lookup contexts. auto* self_witness = arena_->New( impl_decl, Bindings::SymbolicIdentity(arena_, generic_bindings)); - // Compute a witness that the impl implements its constraint. - Nonnull impl_witness; - { + // Check that this impl satisfies its constraints and push it into the + // ImplScope. For a templated impl, only the template is pushed into scope. + // Instantiations are found by substituting arguments into the parameterized + // ImplWitness. + if (!is_template_instantiation) { + // Compute a witness that the impl implements its constraint. std::vector rewrite_constraints_as_equality_constraints; ImplScope self_impl_scope(&impl_scope); @@ -5609,22 +5702,16 @@ auto TypeChecker::DeclareImplDeclaration(Nonnull impl_decl, // Ensure that's enough for our interface to be satisfied. CARBON_ASSIGN_OR_RETURN( - impl_witness, self_impl_scope.Resolve(constraint_type, impl_type_value, - impl_decl->source_loc(), *this)); - } + Nonnull impl_witness, + self_impl_scope.Resolve(constraint_type, impl_type_value, + impl_decl->source_loc(), *this)); - // Declare the impl members. An `impl` behaves like a class scope. - ScopeInfo impl_scope_info = - ScopeInfo::ForClassScope(scope_info, &impl_scope, generic_bindings); - for (Nonnull m : impl_decl->members()) { - CARBON_RETURN_IF_ERROR(DeclareDeclaration(m, impl_scope_info)); + // Create the implied impl bindings. + CARBON_RETURN_IF_ERROR(CheckAndAddImplBindings( + impl_decl, impl_type_value, self_witness, impl_witness, + generic_bindings, impl_scope_info)); } - // Create the implied impl bindings. - CARBON_RETURN_IF_ERROR( - CheckAndAddImplBindings(impl_decl, impl_type_value, self_witness, - impl_witness, generic_bindings, impl_scope_info)); - if (trace_stream_->is_enabled()) { *trace_stream_ << "** finished declaring impl " << *impl_decl->impl_type() << " as " << impl_decl->interface() << "\n"; @@ -5663,6 +5750,13 @@ void TypeChecker::BringAssociatedConstantsIntoScope( auto TypeChecker::TypeCheckImplDeclaration(Nonnull impl_decl, const ImplScope& enclosing_scope) -> ErrorOr { + if (!IsTemplateSaturated(impl_decl->deduced_parameters())) { + if (trace_stream_->is_enabled()) { + *trace_stream_ << "deferring checking templated " << *impl_decl << "\n"; + } + return Success(); + } + if (trace_stream_->is_enabled()) { *trace_stream_ << "checking " << *impl_decl << "\n"; } @@ -5958,12 +6052,14 @@ auto TypeChecker::DeclareDeclaration(Nonnull d, } case DeclarationKind::ImplDeclaration: { auto& impl_decl = cast(*d); - CARBON_RETURN_IF_ERROR(DeclareImplDeclaration(&impl_decl, scope_info)); + CARBON_RETURN_IF_ERROR(DeclareImplDeclaration( + &impl_decl, scope_info, /*is_template_instantiation=*/false)); break; } case DeclarationKind::MatchFirstDeclaration: { for (auto* impl : cast(d)->impls()) { - CARBON_RETURN_IF_ERROR(DeclareImplDeclaration(impl, scope_info)); + CARBON_RETURN_IF_ERROR(DeclareImplDeclaration( + impl, scope_info, /*is_template_instantiation=*/false)); } break; } @@ -6151,4 +6247,78 @@ auto TypeChecker::FindCollectedMembers(Nonnull decl) } } +auto TypeChecker::InstantiateImplDeclaration( + Nonnull old_impl, + Nonnull bindings) const + -> ErrorOr, Nonnull>> { + CARBON_CHECK(IsTemplateSaturated(*bindings)); + + if (trace_stream_->is_enabled()) { + *trace_stream_ << "instantiating " << *old_impl; + } + + auto it = templates_.find(old_impl); + CARBON_CHECK(it != templates_.end()); + const TemplateInfo& info = it->second; + + // TODO: Only instantiate each declaration once for each set of template + // arguments. + CloneContext context(arena_); + Nonnull impl = + context.Clone(cast(info.pattern)); + + // Update the binding to store its instantiated value or a link back to the + // original generic parameter. + Bindings new_bindings; + for (auto [param, value] : bindings->args()) { + auto param_it = info.param_map.find(param); + CARBON_CHECK(param_it != info.param_map.end()); + + auto* clone = context.GetExistingClone(param_it->second); + switch (param->binding_kind()) { + case GenericBinding::BindingKind::Template: { + clone->set_template_value(value); + // TODO: Set a constant value on the impl binding too, if there is one. + break; + } + + case GenericBinding::BindingKind::Checked: { + std::optional> witness; + if (auto impl = param->impl_binding()) { + auto it = bindings->witnesses().find(*impl); + CARBON_CHECK(it != bindings->witnesses().end()) + << "no witness for generic binding"; + witness = it->second; + } + new_bindings.Add(clone, value, witness); + break; + } + } + } + + // TODO: It's probably not correct to use the top-level impl scope here. It's + // not obvious what we should use, though -- which impls are in scope in + // template instantiation? + CARBON_CHECK(top_level_impl_scope_) + << "can't perform template instantiation with no top-level scope"; + ImplScope scope(*top_level_impl_scope_); + + // TODO: Remove the const-cast here. The requirement to perform template + // instantiation unfortunately means that a lot of type-checking stops being + // free of side-effects, so this means removing `const` throughout most of + // the type-checker. + auto* type_checker = const_cast(this); + + // Type-check the new impl. + // + // TODO: Augment any error we see here with an "instantiation failed" note + // pointing to the location where the instantiation was required. + CARBON_RETURN_IF_ERROR(type_checker->DeclareImplDeclaration( + impl, ScopeInfo::ForNonClassScope(&scope), + /*is_template_instantiation=*/true)); + CARBON_RETURN_IF_ERROR(type_checker->TypeCheckImplDeclaration(impl, scope)); + + return std::pair{impl, arena_->New(std::move(new_bindings))}; +} + } // namespace Carbon diff --git a/explorer/interpreter/type_checker.h b/explorer/interpreter/type_checker.h index 02d3921c8001..25dc41bfc714 100644 --- a/explorer/interpreter/type_checker.h +++ b/explorer/interpreter/type_checker.h @@ -280,7 +280,9 @@ class TypeChecker { const ScopeInfo& scope_info) -> ErrorOr; auto DeclareImplDeclaration(Nonnull impl_decl, - const ScopeInfo& scope_info) -> ErrorOr; + const ScopeInfo& scope_info, + bool is_template_instantiation) + -> ErrorOr; auto DeclareChoiceDeclaration(Nonnull choice, const ScopeInfo& scope_info) @@ -519,6 +521,12 @@ class TypeChecker { auto FindCollectedMembers(Nonnull decl) -> CollectedMembersMap&; + // Instantiate an impl with the given set of bindings, including one or more + // template bindings. + ErrorOr, Nonnull>> + InstantiateImplDeclaration(Nonnull pattern, + Nonnull bindings) const; + Nonnull arena_; Builtins builtins_; @@ -541,6 +549,21 @@ class TypeChecker { // the `const`s from everywhere that transitively does `impl` matching to get // rid of this `mutable`. mutable MatchingImplSet matching_impl_set_; + + // Information about a generic that has one or more template parameters. + struct TemplateInfo { + // The original pattern, prior to any type-checking. + Nonnull pattern; + // A mapping from the bindings of the type-checked pattern to the bindings + // of the original. + std::map param_map; + // TODO: Keep track of the instantiations we've already performed and don't + // do them again. + }; + + // Map from template declarations to extra information we use to type-check + // and instantiate the template. + std::map templates_; }; } // namespace Carbon diff --git a/explorer/syntax/lexer.lpp b/explorer/syntax/lexer.lpp index 2ca5264446c9..c5e8fb1cbbb4 100644 --- a/explorer/syntax/lexer.lpp +++ b/explorer/syntax/lexer.lpp @@ -121,6 +121,7 @@ SLASH "/" SLASH_EQUAL "/=" STAR_EQUAL "*=" STRING "String" +TEMPLATE "template" THEN "then" TRUE "true" TYPE "type" @@ -240,6 +241,7 @@ operand_start [(A-Za-z0-9_\"] {SLASH} { return CARBON_SIMPLE_TOKEN(SLASH); } {STAR_EQUAL} { return CARBON_SIMPLE_TOKEN(STAR_EQUAL); } {STRING} { return CARBON_SIMPLE_TOKEN(STRING); } +{TEMPLATE} { return CARBON_SIMPLE_TOKEN(TEMPLATE); } {THEN} { return CARBON_SIMPLE_TOKEN(THEN); } {TRUE} { return CARBON_SIMPLE_TOKEN(TRUE); } {TYPE} { return CARBON_SIMPLE_TOKEN(TYPE); } diff --git a/explorer/syntax/parser.ypp b/explorer/syntax/parser.ypp index 8580d17c0252..3c5963719b4d 100644 --- a/explorer/syntax/parser.ypp +++ b/explorer/syntax/parser.ypp @@ -298,6 +298,7 @@ SLASH_EQUAL STAR_EQUAL STRING + TEMPLATE THEN TRUE TYPE @@ -741,7 +742,8 @@ where_expression: type_expression WHERE where_clause_list { auto* self = - arena->New(context.source_loc(), ".Self", $1); + arena->New(context.source_loc(), ".Self", $1, + GenericBinding::BindingKind::Checked); $$ = arena->New(context.source_loc(), self, $3); } ; @@ -850,7 +852,15 @@ non_expression_pattern: std::nullopt); } | binding_lhs COLON_BANG expression - { $$ = arena->New(context.source_loc(), $1, $3); } + { + $$ = arena->New(context.source_loc(), $1, $3, + GenericBinding::BindingKind::Checked); + } +| TEMPLATE binding_lhs COLON_BANG expression + { + $$ = arena->New(context.source_loc(), $2, $4, + GenericBinding::BindingKind::Template); + } | paren_pattern { $$ = $1; } | postfix_expression tuple_pattern @@ -1083,7 +1093,13 @@ return_term: generic_binding: identifier COLON_BANG expression { - $$ = arena->New(context.source_loc(), std::move($1), $3); + $$ = arena->New(context.source_loc(), std::move($1), $3, + GenericBinding::BindingKind::Checked); + } +| TEMPLATE identifier COLON_BANG expression + { + $$ = arena->New(context.source_loc(), std::move($2), $4, + GenericBinding::BindingKind::Template); } ; deduced_param: @@ -1253,7 +1269,9 @@ declaration: | MIXIN declared_name type_params mixin_import LEFT_CURLY_BRACE mixin_body RIGHT_CURLY_BRACE { // EXPERIMENTAL MIXN FEATURE - auto self = arena->New(context.source_loc(), "Self", $4); + auto self = + arena->New(context.source_loc(), "Self", $4, + GenericBinding::BindingKind::Checked); $$ = arena->New(context.source_loc(), std::move($2), $3, self, $6); } diff --git a/explorer/testdata/assoc_const/fail_anonymous.carbon b/explorer/testdata/assoc_const/fail_anonymous.carbon index a26a76355b2c..4da898e2787e 100644 --- a/explorer/testdata/assoc_const/fail_anonymous.carbon +++ b/explorer/testdata/assoc_const/fail_anonymous.carbon @@ -9,7 +9,7 @@ package ExplorerTest api; interface Iface { - // CHECK:STDERR: SYNTAX ERROR: {{.*}}/explorer/testdata/assoc_const/fail_anonymous.carbon:[[@LINE+1]]: syntax error, unexpected UNDERSCORE, expecting identifier + // CHECK:STDERR: SYNTAX ERROR: {{.*}}/explorer/testdata/assoc_const/fail_anonymous.carbon:[[@LINE+1]]: syntax error, unexpected UNDERSCORE, expecting identifier or TEMPLATE let _:! type; } diff --git a/explorer/testdata/template/fail_name_lookup.carbon b/explorer/testdata/template/fail_name_lookup.carbon new file mode 100644 index 000000000000..6a06218e5771 --- /dev/null +++ b/explorer/testdata/template/fail_name_lookup.carbon @@ -0,0 +1,46 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// AUTOUPDATE +// RUN: %{not} %{explorer-run} +// RUN: %{not} %{explorer-run-trace} + +package ExplorerTest api; + +interface CallF { + fn DoIt[self: Self](); +} + +interface HasF { + fn F[self: Self](); +} + +impl forall [template T:! HasF] T as CallF { + // TODO: This case should be accepted, using `ClassWithExternalF.(HasF.F)`. + // TODO: The other case should be rejected due to ambiguity. + // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/template/fail_name_lookup.carbon:[[@LINE+1]]: class ClassWithExternalF does not have a field named F + fn DoIt[self: Self]() { self.F(); } +} + +class ClassWithInternalF { + fn F[self: Self]() { Print("ClassWithInternalF.F"); } +} + +external impl ClassWithInternalF as HasF { + fn F[self: Self]() { Print("ClassWithInternalF.(HasF.F)"); } +} + +class ClassWithExternalF {} + +external impl ClassWithExternalF as HasF { + fn F[self: Self]() { Print("ClassWithExternalF.(HasF.F)"); } +} + +fn Main() -> i32 { + var a: ClassWithInternalF = {}; + var b: ClassWithExternalF = {}; + a.(CallF.DoIt)(); + b.(CallF.DoIt)(); + return 0; +} diff --git a/explorer/testdata/template/fail_no_member.carbon b/explorer/testdata/template/fail_no_member.carbon new file mode 100644 index 000000000000..cd63b893917f --- /dev/null +++ b/explorer/testdata/template/fail_no_member.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: %{not} %{explorer-run} +// RUN: %{not} %{explorer-run-trace} + +package ExplorerTest api; + +interface GetX { + fn DoIt[self: Self]() -> i32; +} + +impl forall [template T:! type] T as GetX { + // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/template/fail_no_member.carbon:[[@LINE+1]]: member access, unexpected i32 in self.x + fn DoIt[self: Self]() -> i32 { return self.x; } +} + +fn Main() -> i32 { + return 0.(GetX.DoIt)(); +} diff --git a/explorer/testdata/template/member_access.carbon b/explorer/testdata/template/member_access.carbon new file mode 100644 index 000000000000..8e58bdd65f17 --- /dev/null +++ b/explorer/testdata/template/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: %{explorer-run} +// RUN: %{explorer-run-trace} +// CHECK:STDOUT: 1 +// CHECK:STDOUT: 3 +// CHECK:STDOUT: result: 0 + +package ExplorerTest api; + +interface GetX { + fn DoIt[self: Self]() -> i32; +} + +impl forall [template T:! type] T as GetX { + fn DoIt[self: Self]() -> i32 { return self.x; } +} + +class C { + var x: i32; +} + +fn Main() -> i32 { + var a: auto = {.x = 1, .y = 2}; + var b: C = {.x = 3}; + Print("{0}", a.(GetX.DoIt)()); + Print("{0}", b.(GetX.DoIt)()); + return 0; +} diff --git a/explorer/testdata/template/name_lookup.carbon b/explorer/testdata/template/name_lookup.carbon new file mode 100644 index 000000000000..c7c9c171a795 --- /dev/null +++ b/explorer/testdata/template/name_lookup.carbon @@ -0,0 +1,77 @@ +// 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: MemberF.F +// CHECK:STDOUT: ImplF.(HasF.F) +// CHECK:STDOUT: BothFs.(HasF.F) +// CHECK:STDOUT: BothFs.F +// CHECK:STDOUT: BothFs.F +// CHECK:STDOUT: result: 0 + +package ExplorerTest api; + +choice ImplKind { + Checked, + ConstrainedTemplate, + UnconstrainedTemplate +} + +interface CallF(K:! ImplKind) { + fn DoIt[self: Self](); +} + +interface HasF { + fn F[self: Self](); +} + +impl forall [T:! HasF] T as CallF(ImplKind.Checked) { + fn DoIt[self: Self]() { self.F(); } +} + +impl forall [template T:! HasF] T as CallF(ImplKind.ConstrainedTemplate) { + fn DoIt[self: Self]() { self.F(); } +} + +impl forall [template T:! type] T as CallF(ImplKind.UnconstrainedTemplate) { + fn DoIt[self: Self]() { self.F(); } +} + +class MemberF { + fn F[self: Self]() { Print("MemberF.F"); } +} + +class ImplF {} +external impl ImplF as HasF { + fn F[self: Self]() { Print("ImplF.(HasF.F)"); } +} + +class BothFs { + fn F[self: Self]() { Print("BothFs.F"); } +} +external impl BothFs as HasF { + fn F[self: Self]() { Print("BothFs.(HasF.F)"); } +} + +fn Main() -> i32 { + var mem: MemberF = {}; + var imp: ImplF = {}; + var both: BothFs = {}; + + mem.(CallF(ImplKind.UnconstrainedTemplate).DoIt)(); + + imp.(CallF(ImplKind.Checked).DoIt)(); + // TODO: Should be valid, but currently fails during instantiation. + //imp.(CallF(ImplKind.ConstrainedTemplate).DoIt)(); + + both.(CallF(ImplKind.Checked).DoIt)(); + // TODO: Should be rejected, but currently incorrectly accepted. + // This line can be deleted once it starts failing; we test that this is + // rejected in fail_name_lookup.carbon. + both.(CallF(ImplKind.ConstrainedTemplate).DoIt)(); + both.(CallF(ImplKind.UnconstrainedTemplate).DoIt)(); + return 0; +}