From f46eaf49a2e3ba2525a7cc8df8555da9a7508fbf Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Wed, 26 Oct 2022 14:49:17 -0700 Subject: [PATCH] Compute a fixed point of rewrite constraints. (#2350) When resolving a constraint type, compute the fixed point of the specified rewrite constraints, as requested by @josh11b in review of #2173 and tentatively agreed as our direction. If no such fixed point exists, detect that situation and diagnose the problem. The algorithm used here is to iteratively apply all rewrites to each rewrite constraint until either one of them refers to itself, which indicates there's a cycle in the rewrite graph, or the set of rewrites converges. This algorithm has some pathological inputs in which the runtime can grow exponentially in the size of the input (indeed, the fully-rewritten set of rewrites can grow exponentially in the size of the input, so this is unavoidable), so an iteration limit is also provided. --- explorer/interpreter/interpreter.cpp | 6 +- explorer/interpreter/type_checker.cpp | 354 ++++++++++++------ explorer/interpreter/type_checker.h | 11 + .../fail_rewrite_creates_infinite_type.carbon | 20 + .../assoc_const/fail_rewrite_cycle.carbon | 40 ++ .../fail_simple_rewrite_cycle_1.carbon | 23 ++ .../fail_simple_rewrite_cycle_2.carbon | 23 ++ .../assoc_const/resolve_rewrites.carbon | 58 +++ .../rewrite_interface_params.carbon | 46 +++ .../assoc_const/rewrite_large_type.carbon | 89 +++++ 10 files changed, 560 insertions(+), 110 deletions(-) create mode 100644 explorer/testdata/assoc_const/fail_rewrite_creates_infinite_type.carbon create mode 100644 explorer/testdata/assoc_const/fail_rewrite_cycle.carbon create mode 100644 explorer/testdata/assoc_const/fail_simple_rewrite_cycle_1.carbon create mode 100644 explorer/testdata/assoc_const/fail_simple_rewrite_cycle_2.carbon create mode 100644 explorer/testdata/assoc_const/resolve_rewrites.carbon create mode 100644 explorer/testdata/assoc_const/rewrite_interface_params.carbon create mode 100644 explorer/testdata/assoc_const/rewrite_large_type.carbon diff --git a/explorer/interpreter/interpreter.cpp b/explorer/interpreter/interpreter.cpp index 33944410f69b..8950575ed88a 100644 --- a/explorer/interpreter/interpreter.cpp +++ b/explorer/interpreter/interpreter.cpp @@ -723,7 +723,8 @@ auto Interpreter::Convert(Nonnull value, return arena_->New(inst_dest, value); } default: { - CARBON_CHECK(IsValueKindDependent(destination_type)) + CARBON_CHECK(IsValueKindDependent(destination_type) || + isa(destination_type)) << "Can't convert value " << *value << " to type " << *destination_type; return value; @@ -758,7 +759,8 @@ auto Interpreter::Convert(Nonnull value, break; } default: { - CARBON_CHECK(IsValueKindDependent(destination_type)) + CARBON_CHECK(IsValueKindDependent(destination_type) || + isa(destination_type)) << "Can't convert value " << *value << " to type " << *destination_type; return value; diff --git a/explorer/interpreter/type_checker.cpp b/explorer/interpreter/type_checker.cpp index 8abaf8bd8982..13a599dad610 100644 --- a/explorer/interpreter/type_checker.cpp +++ b/explorer/interpreter/type_checker.cpp @@ -5,6 +5,7 @@ #include "explorer/interpreter/type_checker.h" #include +#include #include #include #include @@ -1094,6 +1095,45 @@ auto TypeChecker::ArgumentDeduction::Finish(TypeChecker& type_checker, return std::move(bindings); } +// Look for a rewrite to use when naming the given interface member in a type +// that has the given list of rewrites. +static auto LookupRewrite(llvm::ArrayRef rewrites, + Nonnull interface, + Nonnull member) + -> std::optional { + if (!isa(member)) { + return std::nullopt; + } + + for (auto& rewrite : rewrites) { + if (ValueEqual(interface, &rewrite.constant->interface(), std::nullopt) && + member == &rewrite.constant->constant()) { + // A ConstraintType can only have one rewrite per (interface, member) + // pair, so we don't need to check the rest. + return &rewrite; + } + } + + return std::nullopt; +} + +// Look for a rewrite to use when naming the given interface member in a type +// declared with the given type-of-type. +static auto LookupRewrite(Nonnull type_of_type, + Nonnull interface, + Nonnull member) + -> std::optional { + // Find the set of rewrites. Only ConstraintTypes have rewrites. + // TODO: If we can ever see an InterfaceType here, we should convert it to a + // constraint type. + llvm::ArrayRef rewrites; + if (const auto* constraint_type = dyn_cast(type_of_type)) { + rewrites = constraint_type->rewrite_constraints(); + } + + return LookupRewrite(rewrites, interface, member); +} + // Builder for constraint types. // // This type supports incrementally building a constraint type by adding @@ -1103,6 +1143,15 @@ auto TypeChecker::ArgumentDeduction::Finish(TypeChecker& type_checker, // canonicalization or hashing or similar to speed this up. class TypeChecker::ConstraintTypeBuilder { public: + // Information about a rewrite constraint that is currently being rewritten. + struct RewriteInfo { + Nonnull rewrite; + // Whether the rewrite has been found to refer to itself. If so, the + // self-reference will not be expanded. `Resolve` uses this to detect + // rewrites that cannot be resolved due to cycles. + bool rewrite_references_itself = false; + }; + ConstraintTypeBuilder(Nonnull arena, SourceLocation source_loc) : ConstraintTypeBuilder(arena, MakeSelfBinding(arena, source_loc)) {} ConstraintTypeBuilder(Nonnull arena, @@ -1127,6 +1176,10 @@ class TypeChecker::ConstraintTypeBuilder { return rewrite_constraints_; } + auto current_rewrite_info() -> std::optional> { + return current_rewrite_info_; + } + // Produces a type that refers to the `.Self` type of the constraint. auto GetSelfType() const -> Nonnull { return *self_binding_->symbolic_identity(); @@ -1225,8 +1278,6 @@ class TypeChecker::ConstraintTypeBuilder { // constraint, then rewrites for this added constraint become rewrites for // the resulting constraint. Otherwise, discard the rewrites and keep only // their corresponding equality constraints. - // TODO: What happens if these rewrites appear in the impl constraints? - // TODO: What happens if these rewrites appear in each other? for (const auto& rewrite_constraint : constraint->rewrite_constraints()) { const auto* interface = cast(type_checker.Substitute( local_bindings, &rewrite_constraint.constant->interface())); @@ -1323,55 +1374,9 @@ class TypeChecker::ConstraintTypeBuilder { // declared name. auto Resolve(TypeChecker& type_checker, SourceLocation source_loc, const ImplScope& impl_scope) -> ErrorOr { - type_checker.partial_constraint_types_.push_back(this); - auto pop_partial_constraint_type = llvm::make_scope_exit( - [&] { type_checker.partial_constraint_types_.pop_back(); }); - - // Check for conflicting rewrites. - // TODO: Avoid the quadratic behavior. - for (int i = 0; i != static_cast(rewrite_constraints_.size()); ++i) { - auto& rewrite_a = rewrite_constraints_[i]; - for (auto rewrite_b : - llvm::makeArrayRef(rewrite_constraints_).drop_front(i + 1)) { - if (ValueEqual(rewrite_a.constant, rewrite_b.constant, std::nullopt)) { - if (ValueEqual(rewrite_a.unconverted_replacement, - rewrite_b.unconverted_replacement, std::nullopt) && - TypeEqual(rewrite_a.unconverted_replacement_type, - rewrite_b.unconverted_replacement_type, std::nullopt)) { - // We don't need to do any more checks for this `rewrite_a` value. - // `rewrite_b` will be compared against the next matching value, - // and so on. - break; - } - return ProgramError(source_loc) - << "multiple different rewrites for `" << *rewrite_a.constant - << "`:\n" - << " " << *rewrite_a.unconverted_replacement << "\n" - << " " << *rewrite_b.unconverted_replacement; - } - } - } - - // Rewrite `Self.X is Y` to `Replacement is Y` if we have a rewrite for - // `Self.X`. - // TODO: Properly apply rewrites throughout all the constraints. Check for - // cycles. This is just a very short-term hack. - for (auto& impl_constraint : impl_constraints_) { - bool performed_rewrite; - do { - performed_rewrite = false; - if (const auto* assoc = - dyn_cast(impl_constraint.type)) { - for (const auto& rewrite : rewrite_constraints_) { - if (ValueEqual(assoc, rewrite.constant, std::nullopt)) { - impl_constraint.type = rewrite.converted_replacement; - performed_rewrite = true; - } - } - } - } while (performed_rewrite); - } - + CARBON_RETURN_IF_ERROR(DeduplicateRewrites(source_loc)); + CARBON_RETURN_IF_ERROR(ApplyRewritesToRewrites(type_checker, source_loc)); + ApplyRewritesToConstraints(type_checker); return Success(); } @@ -1422,6 +1427,141 @@ class TypeChecker::ConstraintTypeBuilder { return impl_binding; } + // Check for conflicting rewrites and deduplicate. + auto DeduplicateRewrites(SourceLocation source_loc) -> ErrorOr { + std::vector new_rewrite_constraints; + for (auto& rewrite_a : rewrite_constraints_) { + if (auto existing_rewrite = LookupRewrite( + new_rewrite_constraints, &rewrite_a.constant->interface(), + &rewrite_a.constant->constant())) { + auto& rewrite_b = **existing_rewrite; + if (ValueEqual(rewrite_a.unconverted_replacement, + rewrite_b.unconverted_replacement, std::nullopt) && + TypeEqual(rewrite_a.unconverted_replacement_type, + rewrite_b.unconverted_replacement_type, std::nullopt)) { + // This is a duplicate, ignore it. + continue; + } + return ProgramError(source_loc) + << "multiple different rewrites for `" << *rewrite_a.constant + << "`:\n" + << " " << *rewrite_b.unconverted_replacement << "\n" + << " " << *rewrite_a.unconverted_replacement; + } + new_rewrite_constraints.push_back(rewrite_a); + } + rewrite_constraints_ = std::move(new_rewrite_constraints); + return Success(); + } + + // Apply rewrites to each other and find a fixed point, or diagnose if there + // is a cycle. + auto ApplyRewritesToRewrites(TypeChecker& type_checker, + SourceLocation source_loc) -> ErrorOr { + // Add this builder to the type checker's scope so that it considers our + // rewrites. + type_checker.partial_constraint_types_.push_back(this); + auto pop_partial_constraint_type = llvm::make_scope_exit( + [&] { type_checker.partial_constraint_types_.pop_back(); }); + + std::deque> rewrite_queue; + for (auto& rewrite : rewrite_constraints_) { + if (type_checker.trace_stream_) { + **type_checker.trace_stream_ << "initial rewrite of " + << *rewrite.constant << " is " + << *rewrite.converted_replacement << "\n"; + } + rewrite_queue.push_back(&rewrite); + } + + int rewrite_iterations = 0; + while (!rewrite_queue.empty()) { + auto* rewrite = rewrite_queue.front(); + rewrite_queue.pop_front(); + + // This iteration limit exists only to prevent large fuzzer-generated + // examples from leading to long compile times. If the limit is hit in a + // real example, it should be increased. + if (rewrite_iterations > 1000) { + return ProgramError(source_loc) + << "reached iteration limit resolving rewrite constraints"; + } + ++rewrite_iterations; + + // Rebuild the rewrite and see if it changed. Also track whether it + // attempted to reference itself recursively. + RewriteInfo info = {.rewrite = rewrite}; + current_rewrite_info_ = &info; + Nonnull rebuilt = + type_checker.RebuildValue(rewrite->converted_replacement); + current_rewrite_info_ = std::nullopt; + + if (info.rewrite_references_itself) { + // This would result in an infinite loop. + return ProgramError(source_loc) + << "rewrite of " << *rewrite->constant + << " applies within its own resolved expansion of " << *rebuilt; + } + + if (!ValueEqual(rebuilt, rewrite->converted_replacement, std::nullopt)) { + if (type_checker.trace_stream_) { + **type_checker.trace_stream_ << "rewrote rewrite of " + << *rewrite->constant << " to " + << *rebuilt << "\n"; + } + rewrite->converted_replacement = rebuilt; + // Now we've rewritten this rewrite, we might find more rewrites apply + // to the portion we rewrote. + rewrite_queue.push_back(rewrite); + } else { + if (type_checker.trace_stream_) { + **type_checker.trace_stream_ << "rewrite of " << *rewrite->constant + << " converged to " << *rebuilt << "\n"; + } + } + } + + return Success(); + } + + // Apply the rewrite constraints throughout our constraints. + auto ApplyRewritesToConstraints(TypeChecker& type_checker) -> void { + // Add this builder to the type checker's scope so that it considers our + // rewrites. + type_checker.partial_constraint_types_.push_back(this); + auto pop_partial_constraint_type = llvm::make_scope_exit( + [&] { type_checker.partial_constraint_types_.pop_back(); }); + + // Apply rewrites through the rewrite constraints. We assume that the + // converted replacements have already been rewritten fully. + for (auto& rewrite : rewrite_constraints_) { + rewrite.unconverted_replacement = + type_checker.RebuildValue(rewrite.unconverted_replacement); + rewrite.unconverted_replacement_type = + type_checker.RebuildValue(rewrite.unconverted_replacement_type); + } + + // Apply rewrites throughout impl constraints. + for (auto& impl_constraint : impl_constraints_) { + impl_constraint.type = type_checker.RebuildValue(impl_constraint.type); + impl_constraint.interface = cast( + type_checker.RebuildValue(impl_constraint.interface)); + } + + // Apply rewrites throughout equality constraints. + for (auto& equality_constraint : equality_constraints_) { + for (auto*& value : equality_constraint.values) { + value = type_checker.RebuildValue(value); + } + } + + // Apply rewrites throughout lookup contexts. + for (auto& lookup_context : lookup_contexts_) { + lookup_context.context = + type_checker.RebuildValue(lookup_context.context); + } + } + Nonnull arena_; Nonnull self_binding_; Nonnull impl_binding_; @@ -1429,6 +1569,7 @@ class TypeChecker::ConstraintTypeBuilder { std::vector equality_constraints_; std::vector rewrite_constraints_; std::vector lookup_contexts_; + std::optional current_rewrite_info_; }; // A collection of substituted `GenericBinding`s and `ImplBinding`s. @@ -1499,15 +1640,39 @@ auto TypeChecker::Substitute(const Bindings& bindings, return type; } + auto* result = SubstituteImpl(bindings, type); + + if (trace_stream_) { + **trace_stream_ << "substitution of {"; + llvm::ListSeparator sep; + for (const auto& [name, value] : bindings.args()) { + **trace_stream_ << sep << *name << " -> " << *value; + } + for (const auto& [name, value] : bindings.witnesses()) { + **trace_stream_ << sep << *name << " -> " << *value; + } + **trace_stream_ << "}\n old: " << *type << "\n new: " << *result << "\n"; + } + return result; +} + +auto TypeChecker::RebuildValue(Nonnull value) const + -> Nonnull { + return SubstituteImpl(Bindings(), value); +} + +auto TypeChecker::SubstituteImpl(const Bindings& bindings, + Nonnull type) const + -> Nonnull { auto substitute_into_bindings = [&](Nonnull inner_bindings) -> Nonnull { BindingMap values; for (const auto& [name, value] : inner_bindings->args()) { - values[name] = Substitute(bindings, value); + values[name] = SubstituteImpl(bindings, value); } ImplWitnessMap witnesses; for (const auto& [name, value] : inner_bindings->witnesses()) { - witnesses[name] = Substitute(bindings, value); + witnesses[name] = SubstituteImpl(bindings, value); } if (values == inner_bindings->args() && witnesses == inner_bindings->witnesses()) { @@ -1531,9 +1696,9 @@ auto TypeChecker::Substitute(const Bindings& bindings, } case Value::Kind::AssociatedConstant: { const auto& assoc = cast(*type); - Nonnull base = Substitute(bindings, &assoc.base()); + Nonnull base = SubstituteImpl(bindings, &assoc.base()); const auto* interface = - cast(Substitute(bindings, &assoc.interface())); + cast(SubstituteImpl(bindings, &assoc.interface())); // If we're substituting into an associated constant, we may now be able // to rewrite it to a concrete value. if (auto rewritten_value = @@ -1541,7 +1706,7 @@ auto TypeChecker::Substitute(const Bindings& bindings, return (*rewritten_value)->converted_replacement; } const auto* witness = - cast(Substitute(bindings, &assoc.witness())); + cast(SubstituteImpl(bindings, &assoc.witness())); witness = RefineWitness(witness, base, interface); if (auto rewritten_value = LookupRewriteInWitness(witness, interface, &assoc.constant())) { @@ -1553,14 +1718,14 @@ auto TypeChecker::Substitute(const Bindings& bindings, case Value::Kind::TupleValue: { std::vector> elts; for (const auto& elt : cast(*type).elements()) { - elts.push_back(Substitute(bindings, elt)); + elts.push_back(SubstituteImpl(bindings, elt)); } return arena_->New(elts); } case Value::Kind::StructType: { std::vector fields; for (const auto& [name, value] : cast(*type).fields()) { - const auto* new_type = Substitute(bindings, value); + const auto* new_type = SubstituteImpl(bindings, value); fields.push_back({name, new_type}); } return arena_->New(std::move(fields)); @@ -1587,9 +1752,9 @@ auto TypeChecker::Substitute(const Bindings& bindings, // Apply substitution to parameter and return types and create the new // function type. const auto* param = - Substitute(subst_bindings.bindings(), &fn_type.parameters()); + SubstituteImpl(subst_bindings.bindings(), &fn_type.parameters()); const auto* ret = - Substitute(subst_bindings.bindings(), &fn_type.return_type()); + SubstituteImpl(subst_bindings.bindings(), &fn_type.return_type()); return arena_->New( param, std::move(generic_parameters), ret, std::move(deduced_bindings), @@ -1597,7 +1762,7 @@ auto TypeChecker::Substitute(const Bindings& bindings, } case Value::Kind::PointerType: { return arena_->New( - Substitute(bindings, &cast(*type).type())); + SubstituteImpl(bindings, &cast(*type).type())); } case Value::Kind::NominalClassType: { const auto& class_type = cast(*type); @@ -1676,7 +1841,7 @@ auto TypeChecker::Substitute(const Bindings& bindings, std::vector> witnesses; witnesses.reserve(witness.witnesses().size()); for (const auto* witness : witness.witnesses()) { - witnesses.push_back(cast(Substitute(bindings, witness))); + witnesses.push_back(cast(SubstituteImpl(bindings, witness))); } return arena_->New(std::move(witnesses)); } @@ -1684,7 +1849,7 @@ auto TypeChecker::Substitute(const Bindings& bindings, const auto& witness = cast(*type); return ConstraintImplWitness::Make( arena_, - cast(Substitute(bindings, witness.constraint_witness())), + cast(SubstituteImpl(bindings, witness.constraint_witness())), witness.index()); } case Value::Kind::StaticArrayType: @@ -1989,45 +2154,6 @@ auto TypeChecker::LookupInConstraint(SourceLocation source_loc, return found.value(); } -// Look for a rewrite to use when naming the given interface member in a type -// that has the given list of rewrites. -static auto LookupRewrite(llvm::ArrayRef rewrites, - Nonnull interface, - Nonnull member) - -> std::optional { - if (!isa(member)) { - return std::nullopt; - } - - for (auto& rewrite : rewrites) { - if (ValueEqual(interface, &rewrite.constant->interface(), std::nullopt) && - member == &rewrite.constant->constant()) { - // A ConstraintType can only have one rewrite per (interface, member) - // pair, so we don't need to check the rest. - return &rewrite; - } - } - - return std::nullopt; -} - -// Look for a rewrite to use when naming the given interface member in a type -// declared with the given type-of-type. -static auto LookupRewrite(Nonnull type_of_type, - Nonnull interface, - Nonnull member) - -> std::optional { - // Find the set of rewrites. Only ConstraintTypes have rewrites. - // TODO: If we can ever see an InterfaceType here, we should convert it to a - // constraint type. - llvm::ArrayRef rewrites; - if (const auto* constraint_type = dyn_cast(type_of_type)) { - rewrites = constraint_type->rewrite_constraints(); - } - - return LookupRewrite(rewrites, interface, member); -} - auto TypeChecker::GetTypeForAssociatedConstant( Nonnull assoc) const -> Nonnull { const auto* assoc_type = &assoc->constant().static_type(); @@ -2048,7 +2174,19 @@ auto TypeChecker::LookupRewriteInTypeOf( // intended semantics in this case. for (auto* builder : partial_constraint_types_) { if (ValueEqual(type, builder->GetSelfType(), std::nullopt)) { - return LookupRewrite(builder->rewrite_constraints(), interface, member); + if (auto result = LookupRewrite(builder->rewrite_constraints(), interface, + member)) { + // If we're in the middle of rewriting this rewrite, let the constraint + // type builder know it applies within itself, and don't expand it + // within itself. + if (auto current_rewrite_info = builder->current_rewrite_info(); + current_rewrite_info && + (*current_rewrite_info)->rewrite == *result) { + (*current_rewrite_info)->rewrite_references_itself = true; + return std::nullopt; + } + return result; + } } } @@ -2057,8 +2195,8 @@ auto TypeChecker::LookupRewriteInTypeOf( if (!var_type->binding().has_static_type()) { // We looked for a rewrite before we finished type-checking the generic // binding. This happens when forming the type of a generic binding. Just - // say there are no rewrites yet. - // TODO: `.Self` substitution should fix this. + // say there are no rewrites yet; any rewrites will be applied when the + // constraint on the binding's type is resolved. return std::nullopt; } return LookupRewrite(&var_type->binding().static_type(), interface, member); @@ -2066,14 +2204,13 @@ auto TypeChecker::LookupRewriteInTypeOf( // Given `(T.U).Y` for an associated type `U`, substitute into the type of // `U` to find rewrites. - // TODO: This substitution can lead to infinite recursion. if (const auto* assoc_const = dyn_cast(type)) { if (!assoc_const->constant().has_static_type()) { // We looked for a rewrite before we finished type-checking the // associated constant. This happens when forming the type of the // associated constant, if `.Self` is used to access an associated - // constant. Just say that there are not rewrites yet. - // TODO: `.Self` substitution should fix this. + // constant. Just say that there are not rewrites yet; any rewrites will + // be applied when the constraint on the binding's type is resolved. return std::nullopt; } // The following is an expanded version of @@ -2093,6 +2230,7 @@ auto TypeChecker::LookupRewriteInTypeOf( if (ValueEqual(interface, Substitute(bindings, &rewrite.constant->interface()), std::nullopt)) { + // TODO: These substitutions can lead to infinite recursion. RewriteConstraint substituted = { // Not substituted, but our callers don't need it. .constant = rewrite.constant, diff --git a/explorer/interpreter/type_checker.h b/explorer/interpreter/type_checker.h index b612d0d8e049..a9d665a5655a 100644 --- a/explorer/interpreter/type_checker.h +++ b/explorer/interpreter/type_checker.h @@ -393,6 +393,17 @@ class TypeChecker { Nonnull actual, const ImplScope& impl_scope) const -> ErrorOr; + // Rebuild a value in the current type-checking context. Applies any rewrites + // that are in scope and attempts to resolve associated constants using impls + // that have been declared since the value was formed. + auto RebuildValue(Nonnull value) const -> Nonnull; + + // Implementation of Substitute and RebuildValue. Does not check that + // bindings are nonempty, nor does it trace its progress. + auto SubstituteImpl(const Bindings& bindings, + Nonnull type) const + -> Nonnull; + // The name of a builtin interface, with any arguments. struct BuiltinInterfaceName { Builtins::Builtin builtin; diff --git a/explorer/testdata/assoc_const/fail_rewrite_creates_infinite_type.carbon b/explorer/testdata/assoc_const/fail_rewrite_creates_infinite_type.carbon new file mode 100644 index 000000000000..97c12301a85a --- /dev/null +++ b/explorer/testdata/assoc_const/fail_rewrite_creates_infinite_type.carbon @@ -0,0 +1,20 @@ +// 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 HasType { + let T:! Type; +} + +// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/assoc_const/fail_rewrite_creates_infinite_type.carbon:[[@LINE+1]]: rewrite of (H).(HasType.T) applies within its own resolved expansion of (H).(HasType.T)* +fn F[H:! HasType where .T = .T*]() {} + +fn Main() -> i32 { + return 0; +} diff --git a/explorer/testdata/assoc_const/fail_rewrite_cycle.carbon b/explorer/testdata/assoc_const/fail_rewrite_cycle.carbon new file mode 100644 index 000000000000..b6b0ab0e05dd --- /dev/null +++ b/explorer/testdata/assoc_const/fail_rewrite_cycle.carbon @@ -0,0 +1,40 @@ +// 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 ManyTypes { + let T0:! Type; + let T1:! Type; + let T2:! Type; + let T3:! Type; + let T4:! Type; + let T5:! Type; + let T6:! Type; + let T7:! Type; + let T8:! Type; + let T9:! Type; +} + +fn F[ + M:! ManyTypes where + .T0 = .T1 and + .T1 = .T2 and + .T2 = .T3 and + .T3 = .T4 and + .T4 = .T5 and + .T5 = .T6 and + .T6 = .T7 and + .T7 = .T8 and + .T8 = .T9 and + // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/assoc_const/fail_rewrite_cycle.carbon:[[@LINE+1]]: rewrite of (M).(ManyTypes.T4) applies within its own resolved expansion of (M).(ManyTypes.T4) + .T9 = .T0]() {} + +fn Main() -> i32 { + return 0; +} diff --git a/explorer/testdata/assoc_const/fail_simple_rewrite_cycle_1.carbon b/explorer/testdata/assoc_const/fail_simple_rewrite_cycle_1.carbon new file mode 100644 index 000000000000..8a64a80b3832 --- /dev/null +++ b/explorer/testdata/assoc_const/fail_simple_rewrite_cycle_1.carbon @@ -0,0 +1,23 @@ +// 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 TwoTypes { + let T:! Type; + let U:! Type; +} + +// Attempting to fully resolve the replacement for `.T` would never create a +// situation where `.T`'s expansion involves `.T`. Ensure we catch this anyway. +// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/assoc_const/fail_simple_rewrite_cycle_1.carbon:[[@LINE+1]]: rewrite of (X).(TwoTypes.U) applies within its own resolved expansion of (X).(TwoTypes.U)* +fn F[X:! TwoTypes where .T = .U and .U = .U*]() {} + +fn Main() -> i32 { + return 0; +} diff --git a/explorer/testdata/assoc_const/fail_simple_rewrite_cycle_2.carbon b/explorer/testdata/assoc_const/fail_simple_rewrite_cycle_2.carbon new file mode 100644 index 000000000000..04cf6be2b1a9 --- /dev/null +++ b/explorer/testdata/assoc_const/fail_simple_rewrite_cycle_2.carbon @@ -0,0 +1,23 @@ +// 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 TwoTypes { + let T:! Type; + let U:! Type; +} + +// Attempting to fully resolve the replacement for `.T` would never create a +// situation where `.T`'s expansion involves `.T`. Ensure we catch this anyway. +// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/assoc_const/fail_simple_rewrite_cycle_2.carbon:[[@LINE+1]]: rewrite of (X).(TwoTypes.U) applies within its own resolved expansion of (X).(TwoTypes.U)* +fn F[X:! TwoTypes where .U = .U* and .T = .U]() {} + +fn Main() -> i32 { + return 0; +} diff --git a/explorer/testdata/assoc_const/resolve_rewrites.carbon b/explorer/testdata/assoc_const/resolve_rewrites.carbon new file mode 100644 index 000000000000..f35e2bc8c4b9 --- /dev/null +++ b/explorer/testdata/assoc_const/resolve_rewrites.carbon @@ -0,0 +1,58 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// AUTOUPDATE +// RUN: %{explorer-run} +// RUN: %{explorer-run-trace} +// CHECK:STDOUT: result: 1 + +package ExplorerTest api; + +interface ManyTypes { + let T0:! Type; + let T1:! Type; + let T2:! Type; + let T3:! Type; + let T4:! Type; + let T5:! Type; + let T6:! Type; + let T7:! Type; + let T8:! Type; + let T9:! Type; +} + +fn F[ + M:! ManyTypes where + .T0 = .T1 and + .T1 = .T2 and + .T2 = .T3 and + .T3 = .T4 and + .T4 = .T5 and + .T5 = .T6 and + .T6 = .T7 and + .T7 = .T8 and + .T8 = .T9 and + .T9 = i32](m: M) -> i32 { + var v: M.T0 = 1; + return v; +} + +class C { + impl as ManyTypes where + .T0 = i32 and + .T1 = .T0 and + .T2 = .T1 and + .T3 = .T2 and + .T4 = .T3 and + .T5 = .T4 and + .T6 = .T5 and + .T7 = .T6 and + .T8 = .T7 and + .T9 = .T8 {} +} + +fn Main() -> i32 { + var c: C = {}; + return F(c); +} diff --git a/explorer/testdata/assoc_const/rewrite_interface_params.carbon b/explorer/testdata/assoc_const/rewrite_interface_params.carbon new file mode 100644 index 000000000000..31c3a1558051 --- /dev/null +++ b/explorer/testdata/assoc_const/rewrite_interface_params.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: %{explorer-run} +// RUN: %{explorer-run-trace} +// CHECK:STDOUT: result: 0 + +package ExplorerTest api; + +interface HasTypes { + let A:! Type; + let B:! Type; + let C:! Type; + let D:! Type; + let E:! Type; + let F:! Type; +} + +interface HasParams(A:! Type, B:! Type, C:! Type, D:! Type) { + let V:! HasTypes; +} + +// Here we discover that there is a rewrite for `HasParams(...).V` only after +// substitution converts each one to `HasParam(X, X, X, X).V`. +fn F[X:! (HasTypes & HasParams(.Self, .Self, .Self, .Self)) where + .Self is HasParams(.A, .A, .A, .A) and + .Self is HasParams(.B, .B, .B, .B) and + .Self is HasParams(.C, .C, .C, .C) and + .Self is HasParams(.D, .D, .D, .D) and + .Self is HasParams(.E, .E, .E, .E) and + .F = .Self.(HasParams(.E, .E, .E, .E).V).A and + .E = .Self.(HasParams(.D, .D, .D, .D).V).A and + .D = .Self.(HasParams(.C, .C, .C, .C).V).A and + .C = .Self.(HasParams(.B, .B, .B, .B).V).A and + .B = .Self.(HasParams(.A, .A, .A, .A).V).A and + .A = .Self and .V = .Self](x: X) -> X.F { return x; } + +impl i32 as HasTypes + where .A = .B and .B = .C and .C = .D and .D = .E and .E = .F and .F = i32 {} +impl i32 as HasParams(i32, i32, i32, i32) where .V = i32 {} + +fn Main() -> i32 { + return F(0); +} diff --git a/explorer/testdata/assoc_const/rewrite_large_type.carbon b/explorer/testdata/assoc_const/rewrite_large_type.carbon new file mode 100644 index 000000000000..0b8da85405eb --- /dev/null +++ b/explorer/testdata/assoc_const/rewrite_large_type.carbon @@ -0,0 +1,89 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// AUTOUPDATE +// RUN: %{explorer-run} +// RUN: %{explorer-run-trace} +// CHECK:STDOUT: result: 1 + +package ExplorerTest api; + +interface ManyTypes { + let T0:! Type; + let T1:! Type; + let T2:! Type; + let T3:! Type; + let T4:! Type; + let T5:! Type; + let T6:! Type; + let T7:! Type; +} + +interface Splat { fn Op(n: i32) -> Self; } +impl i32 as Splat { + fn Op(n: i32) -> Self { return n; } +} +impl forall [T:! Splat] (T, T, T) as Splat { + fn Op(n: i32) -> Self { + let v: T = T.Op(n); + return (v, v, v); + } +} + +fn CallSplat(T:! Splat, n: i32) -> T { return T.Op(n); } + +fn DoSplat( + M:! ManyTypes where + .T0 = (.T1, .T1, .T1) and + .T1 = (.T2, .T2, .T2) and + .T2 = (.T3, .T3, .T3) and + .T3 = (.T4, .T4, .T4) and + .T4 = (.T5, .T5, .T5) and + .T5 = (.T6, .T6, .T6) and + .T6 = (.T7, .T7, .T7) and + .T7 = i32, + n: M.T7) -> M.T0 { + return CallSplat(M.T0, n); +} + +interface First { fn Op[me: Self]() -> i32; } +impl i32 as First { + fn Op[me: Self]() -> i32 { return me; } +} +impl forall [T:! First] (T, T, T) as First { + fn Op[me: Self]() -> i32 { + let (a: T, b: T, c: T) = me; + return a.Op(); + } +} + +fn DoFirst( + M:! ManyTypes where + .T7 = i32 and + .T6 = (.T7, .T7, .T7) and + .T5 = (.T6, .T6, .T6) and + .T4 = (.T5, .T5, .T5) and + .T3 = (.T4, .T4, .T4) and + .T2 = (.T3, .T3, .T3) and + .T1 = (.T2, .T2, .T2) and + .T0 = (.T1, .T1, .T1), + v: M.T0) -> M.T7 { + return v.(First.Op)(); +} + +class C { + impl as ManyTypes where + .T3 = (.T4, .T4, .T4) and + .T1 = (.T2, .T2, .T2) and + .T4 = (.T5, .T5, .T5) and + .T6 = (.T7, .T7, .T7) and + .T2 = (.T3, .T3, .T3) and + .T7 = i32 and + .T5 = (.T6, .T6, .T6) and + .T0 = (.T1, .T1, .T1) {} +} + +fn Main() -> i32 { + return DoFirst(C, DoSplat(C, 1)); +}