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)); +}