Files
carbon-lang/explorer/interpreter/matching_impl_set.cpp
T
Richard Smith 81532c8918 Fix mishandling of struct and tuple types in impl matching termination check. (#2609)
Both cases need a label for each level of struct and tuple type appearing in the impl query, because we can have an unbounded number of such levels with the types otherwise being the same.

Prior to this, both added testcases exhibit unbounded recursion.
2023-02-15 15:10:01 -08:00

171 lines
5.4 KiB
C++

// 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
#include "common/check.h"
#include "common/error.h"
#include "explorer/ast/declaration.h"
#include "explorer/common/error_builders.h"
#include "explorer/common/nonnull.h"
#include "explorer/common/source_location.h"
#include "explorer/interpreter/type_checker.h"
#include "explorer/interpreter/value.h"
namespace Carbon {
// A visitor for type values that collects leaf 'labels', such as class names,
// and adds them to the signature of a `Match` object.
class MatchingImplSet::LeafCollector {
public:
LeafCollector(Match* match) : match_(match) {}
void Collect(const Value* value) {
value->Visit<void>(
[&](const auto* derived_value) { VisitValue(derived_value); });
}
void Collect(Label label) { ++match_->signature_[label]; }
private:
// Most kinds of value don't contribute to the signature.
void VisitValue(const Value*) {}
void VisitValue(const TypeType*) { Collect(Label::TypeType); }
void VisitValue(const BoolType*) { Collect(Label::BoolType); }
void VisitValue(const IntType*) { Collect(Label::IntType); }
void VisitValue(const StringType*) { Collect(Label::StringType); }
void VisitValue(const StaticArrayType* array) {
Collect(Label::ArrayType);
Collect(&array->element_type());
}
void VisitValue(const PointerType* pointer) {
Collect(Label::PointerType);
Collect(&pointer->pointee_type());
}
void VisitValue(const StructType* struct_type) {
Collect(Label::StructType);
for (auto [name, type] : struct_type->fields()) {
Collect(type);
}
}
void VisitValue(const TupleType* tuple_type) {
Collect(Label::TupleType);
for (auto* elem_type : tuple_type->elements()) {
Collect(elem_type);
}
}
void VisitValue(const NominalClassType* class_type) {
VisitDeclarationAndArgs(class_type->declaration(), class_type->bindings());
}
void VisitValue(const MixinPseudoType* mixin_type) {
VisitDeclarationAndArgs(mixin_type->declaration(), mixin_type->bindings());
}
void VisitValue(const InterfaceType* iface_type) {
VisitDeclarationAndArgs(iface_type->declaration(), iface_type->bindings());
}
void VisitValue(const NamedConstraintType* constraint_type) {
VisitDeclarationAndArgs(constraint_type->declaration(),
constraint_type->bindings());
}
void VisitValue(const ChoiceType* choice_type) {
VisitDeclarationAndArgs(choice_type->declaration(),
choice_type->bindings());
}
void VisitDeclarationAndArgs(const Declaration& declaration,
const Bindings& bindings) {
Collect(match_->parent_->GetLabelForDeclaration(declaration));
for (auto [key, value] : bindings.args()) {
Collect(value);
}
}
private:
Match* match_;
};
auto MatchingImplSet::GetLabelForDeclaration(const Declaration& declaration)
-> Label {
auto [it, added] = declaration_labels_.insert(
{&declaration,
Label(int(Label::FirstDeclarationLabel) + declaration_labels_.size())});
return it->second;
}
MatchingImplSet::Match::Match(Nonnull<MatchingImplSet*> parent,
Nonnull<const ImplScope::Impl*> impl,
Nonnull<const Value*> type,
Nonnull<const Value*> interface)
: parent_(parent), impl_(impl), type_(type), interface_(interface) {
// Build our signature.
LeafCollector collector(this);
collector.Collect(type);
collector.Collect(interface);
parent_->matches_.push_back(this);
}
MatchingImplSet::Match::~Match() {
CARBON_CHECK(parent_->matches_.back() == this) << "match stack broken";
parent_->matches_.pop_back();
}
auto MatchingImplSet::Match::DiagnosePotentialCycle(SourceLocation source_loc)
-> ErrorOr<Success> {
for (auto* match : parent_->matches_) {
if (match != this && match->impl_ == impl_) {
// Whether all labels appear a greater or equal number of times in this
// match than in `match`.
bool all_greater_or_equal = true;
// Whether any label appears strictly more times in this match than in
// `match`.
bool any_greater = false;
for (auto [key, value] : signature_) {
int other_value = match->signature_.lookup(key);
if (value < other_value) {
all_greater_or_equal = false;
break;
}
if (value > other_value) {
any_greater = true;
}
}
if (all_greater_or_equal) {
if (any_greater) {
return ProgramError(source_loc)
<< "impl matching recursively performed a more complex match "
"using the same impl\n"
<< " outer match: " << *match->type_ << " as "
<< *match->interface_ << "\n"
<< " inner match: " << *type_ << " as " << *interface_;
}
if (ValueEqual(match->type_, type_, std::nullopt) &&
ValueEqual(match->interface_, interface_, std::nullopt)) {
return ProgramError(source_loc)
<< "impl matching for " << *type_ << " as " << *interface_
<< " recursively performed a match for the same type and "
"interface";
}
}
}
}
return Success();
}
} // namespace Carbon