mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-06 10:54:49 +01:00
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
This commit is contained in:
+11
-6
@@ -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<Error, T> 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_
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -674,7 +674,8 @@ class ConstraintTypeDeclaration : public Declaration {
|
||||
auto* self_type_ref = arena->New<IdentifierExpression>(
|
||||
source_loc, std::string(name_.inner_name()));
|
||||
self_type_ref->set_value_node(self_type_);
|
||||
self_ = arena->New<GenericBinding>(source_loc, "Self", self_type_ref);
|
||||
self_ = arena->New<GenericBinding>(source_loc, "Self", self_type_ref,
|
||||
GenericBinding::BindingKind::Checked);
|
||||
}
|
||||
|
||||
explicit ConstraintTypeDeclaration(CloneContext& context,
|
||||
|
||||
@@ -33,7 +33,17 @@ void Pattern::Print(llvm::raw_ostream& out) const {
|
||||
}
|
||||
case PatternKind::GenericBinding: {
|
||||
const auto& binding = cast<GenericBinding>(*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_)),
|
||||
|
||||
+24
-4
@@ -270,11 +270,19 @@ class GenericBinding : public Pattern {
|
||||
public:
|
||||
using ImplementsCarbonValueNode = void;
|
||||
|
||||
GenericBinding(SourceLocation source_loc, std::string name,
|
||||
Nonnull<Expression*> 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<Expression*> 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<Nonnull<const Value*>> {
|
||||
return std::nullopt;
|
||||
return template_value_;
|
||||
}
|
||||
|
||||
auto symbolic_identity() const -> std::optional<Nonnull<const Value*>> {
|
||||
@@ -310,6 +319,15 @@ class GenericBinding : public Pattern {
|
||||
symbolic_identity_ = value;
|
||||
}
|
||||
|
||||
void set_template_value(Nonnull<const Value*> 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<Nonnull<const ImplBinding*>> {
|
||||
return impl_binding_;
|
||||
@@ -343,7 +361,9 @@ class GenericBinding : public Pattern {
|
||||
private:
|
||||
std::string name_;
|
||||
Nonnull<Expression*> type_;
|
||||
BindingKind binding_kind_;
|
||||
std::optional<int> index_;
|
||||
std::optional<Nonnull<const Value*>> template_value_;
|
||||
std::optional<Nonnull<const Value*>> symbolic_identity_;
|
||||
std::optional<Nonnull<const ImplBinding*>> impl_binding_;
|
||||
std::optional<Nonnull<const GenericBinding*>> original_;
|
||||
|
||||
@@ -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); }
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -1506,7 +1506,8 @@ auto Interpreter::StepExp() -> ErrorOr<Success> {
|
||||
// it from here.
|
||||
auto* self_binding = arena_->New<GenericBinding>(
|
||||
exp.source_loc(), ".Self",
|
||||
arena_->New<TypeTypeLiteral>(exp.source_loc()));
|
||||
arena_->New<TypeTypeLiteral>(exp.source_loc()),
|
||||
GenericBinding::BindingKind::Checked);
|
||||
auto* self = arena_->New<VariableType>(self_binding);
|
||||
auto* impl_binding = arena_->New<ImplBinding>(
|
||||
exp.source_loc(), self_binding, std::nullopt);
|
||||
|
||||
@@ -392,6 +392,56 @@ static auto IsConcreteType(Nonnull<const Value*> 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<const Value*> value) -> bool {
|
||||
// A VariableType is template dependent if it names a template binding.
|
||||
if (auto* var_type = dyn_cast<VariableType>(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<decltype(x), const Value*>) {
|
||||
return IsTemplateDependent(x);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// Any other value is template dependent if any part of it is.
|
||||
return value->Visit<bool>([](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<Nonnull<const GenericBinding*>> 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<NamedValue> 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<GenericBinding>(
|
||||
source_loc, ".Self", arena->New<TypeTypeLiteral>(source_loc));
|
||||
source_loc, ".Self", arena->New<TypeTypeLiteral>(source_loc),
|
||||
GenericBinding::BindingKind::Checked);
|
||||
PrepareSelfBinding(arena, result);
|
||||
return result;
|
||||
}
|
||||
@@ -1945,7 +1996,8 @@ class TypeChecker::SubstitutedGenericBindings {
|
||||
Nonnull<GenericBinding*> new_binding =
|
||||
type_checker_->arena_->New<GenericBinding>(
|
||||
old_binding->source_loc(), old_binding->name(),
|
||||
const_cast<Expression*>(&old_binding->type()));
|
||||
const_cast<Expression*>(&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<const ImplWitness*> witness)
|
||||
-> ErrorOr<Nonnull<const ImplWitness*>> {
|
||||
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<ImplWitness>(declaration, bindings);
|
||||
}
|
||||
|
||||
// For an associated constant, look for a rewrite.
|
||||
auto operator()(Nonnull<const AssociatedConstant*> assoc)
|
||||
-> ErrorOr<Nonnull<const Value*>> {
|
||||
@@ -5505,11 +5575,23 @@ auto TypeChecker::CheckAndAddImplBindings(
|
||||
}
|
||||
|
||||
auto TypeChecker::DeclareImplDeclaration(Nonnull<ImplDeclaration*> impl_decl,
|
||||
const ScopeInfo& scope_info)
|
||||
const ScopeInfo& scope_info,
|
||||
bool is_template_instantiation)
|
||||
-> ErrorOr<Success> {
|
||||
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<Nonnull<const GenericBinding*>> generic_bindings =
|
||||
scope_info.bindings;
|
||||
@@ -5549,8 +5631,9 @@ auto TypeChecker::DeclareImplDeclaration(Nonnull<ImplDeclaration*> impl_decl,
|
||||
Nonnull<const ConstraintType*> constraint_type;
|
||||
{
|
||||
// TODO: Combine this with the SelfDeclaration.
|
||||
auto* self_binding = arena_->New<GenericBinding>(self->source_loc(), "Self",
|
||||
impl_decl->impl_type());
|
||||
auto* self_binding = arena_->New<GenericBinding>(
|
||||
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<ImplBinding>(self_binding->source_loc(),
|
||||
@@ -5578,14 +5661,24 @@ auto TypeChecker::DeclareImplDeclaration(Nonnull<ImplDeclaration*> 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<Declaration*> 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<ImplWitness>(
|
||||
impl_decl, Bindings::SymbolicIdentity(arena_, generic_bindings));
|
||||
|
||||
// Compute a witness that the impl implements its constraint.
|
||||
Nonnull<const Witness*> 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<EqualityConstraint> rewrite_constraints_as_equality_constraints;
|
||||
ImplScope self_impl_scope(&impl_scope);
|
||||
|
||||
@@ -5609,22 +5702,16 @@ auto TypeChecker::DeclareImplDeclaration(Nonnull<ImplDeclaration*> 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<const Witness*> 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<Declaration*> 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<ImplDeclaration*> impl_decl,
|
||||
const ImplScope& enclosing_scope)
|
||||
-> ErrorOr<Success> {
|
||||
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<Declaration*> d,
|
||||
}
|
||||
case DeclarationKind::ImplDeclaration: {
|
||||
auto& impl_decl = cast<ImplDeclaration>(*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<MatchFirstDeclaration>(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<const Declaration*> decl)
|
||||
}
|
||||
}
|
||||
|
||||
auto TypeChecker::InstantiateImplDeclaration(
|
||||
Nonnull<const ImplDeclaration*> old_impl,
|
||||
Nonnull<const Bindings*> bindings) const
|
||||
-> ErrorOr<std::pair<Nonnull<ImplDeclaration*>, Nonnull<Bindings*>>> {
|
||||
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<ImplDeclaration*> impl =
|
||||
context.Clone(cast<ImplDeclaration>(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<Nonnull<const Value*>> 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<TypeChecker*>(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<Bindings>(std::move(new_bindings))};
|
||||
}
|
||||
|
||||
} // namespace Carbon
|
||||
|
||||
@@ -280,7 +280,9 @@ class TypeChecker {
|
||||
const ScopeInfo& scope_info) -> ErrorOr<Success>;
|
||||
|
||||
auto DeclareImplDeclaration(Nonnull<ImplDeclaration*> impl_decl,
|
||||
const ScopeInfo& scope_info) -> ErrorOr<Success>;
|
||||
const ScopeInfo& scope_info,
|
||||
bool is_template_instantiation)
|
||||
-> ErrorOr<Success>;
|
||||
|
||||
auto DeclareChoiceDeclaration(Nonnull<ChoiceDeclaration*> choice,
|
||||
const ScopeInfo& scope_info)
|
||||
@@ -519,6 +521,12 @@ class TypeChecker {
|
||||
auto FindCollectedMembers(Nonnull<const Declaration*> decl)
|
||||
-> CollectedMembersMap&;
|
||||
|
||||
// Instantiate an impl with the given set of bindings, including one or more
|
||||
// template bindings.
|
||||
ErrorOr<std::pair<Nonnull<ImplDeclaration*>, Nonnull<Bindings*>>>
|
||||
InstantiateImplDeclaration(Nonnull<const ImplDeclaration*> pattern,
|
||||
Nonnull<const Bindings*> bindings) const;
|
||||
|
||||
Nonnull<Arena*> 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<const Declaration*> pattern;
|
||||
// A mapping from the bindings of the type-checked pattern to the bindings
|
||||
// of the original.
|
||||
std::map<const GenericBinding*, const GenericBinding*> 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<const Declaration*, TemplateInfo> templates_;
|
||||
};
|
||||
|
||||
} // namespace Carbon
|
||||
|
||||
@@ -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); }
|
||||
|
||||
@@ -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<GenericBinding>(context.source_loc(), ".Self", $1);
|
||||
arena->New<GenericBinding>(context.source_loc(), ".Self", $1,
|
||||
GenericBinding::BindingKind::Checked);
|
||||
$$ = arena->New<WhereExpression>(context.source_loc(), self, $3);
|
||||
}
|
||||
;
|
||||
@@ -850,7 +852,15 @@ non_expression_pattern:
|
||||
std::nullopt);
|
||||
}
|
||||
| binding_lhs COLON_BANG expression
|
||||
{ $$ = arena->New<GenericBinding>(context.source_loc(), $1, $3); }
|
||||
{
|
||||
$$ = arena->New<GenericBinding>(context.source_loc(), $1, $3,
|
||||
GenericBinding::BindingKind::Checked);
|
||||
}
|
||||
| TEMPLATE binding_lhs COLON_BANG expression
|
||||
{
|
||||
$$ = arena->New<GenericBinding>(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<GenericBinding>(context.source_loc(), std::move($1), $3);
|
||||
$$ = arena->New<GenericBinding>(context.source_loc(), std::move($1), $3,
|
||||
GenericBinding::BindingKind::Checked);
|
||||
}
|
||||
| TEMPLATE identifier COLON_BANG expression
|
||||
{
|
||||
$$ = arena->New<GenericBinding>(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<GenericBinding>(context.source_loc(), "Self", $4);
|
||||
auto self =
|
||||
arena->New<GenericBinding>(context.source_loc(), "Self", $4,
|
||||
GenericBinding::BindingKind::Checked);
|
||||
$$ = arena->New<MixinDeclaration>(context.source_loc(), std::move($2), $3,
|
||||
self, $6);
|
||||
}
|
||||
|
||||
+1
-1
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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)();
|
||||
}
|
||||
+32
@@ -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;
|
||||
}
|
||||
+77
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user