Files
carbon-lang/explorer/interpreter/pattern_analysis.cpp
T
Chandler Carruth f75cbea7ce Minor updates to support newer Clang versions. (#4234)
A flag was renamed after LLVM 18, and a warning caught a couple more
trivially fixed issues.
2024-08-21 15:29:45 +00:00

314 lines
10 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 "explorer/interpreter/pattern_analysis.h"
#include <set>
using llvm::cast;
using llvm::dyn_cast;
using llvm::isa;
namespace Carbon {
auto AbstractPattern::kind() const -> Kind {
if (value_.is<const Pattern*>()) {
return Compound;
}
if (const auto* value = value_.dyn_cast<const Value*>()) {
if (isa<TupleValue, AlternativeValue, BoolValue>(value)) {
return Compound;
}
return Primitive;
}
CARBON_CHECK(value_.is<const WildcardTag*>());
return Wildcard;
}
auto AbstractPattern::discriminator() const -> std::string_view {
CARBON_CHECK(kind() == Compound);
if (const auto* pattern = value_.dyn_cast<const Pattern*>()) {
if (const auto* alt_pattern = dyn_cast<AlternativePattern>(pattern)) {
return alt_pattern->alternative_name();
}
} else if (const auto* value = value_.dyn_cast<const Value*>()) {
if (const auto* alt = dyn_cast<AlternativeValue>(value)) {
return alt->alternative().name();
} else if (const auto* bool_val = dyn_cast<BoolValue>(value)) {
return bool_val->value() ? "true" : "false";
}
}
return {};
}
auto AbstractPattern::elements_size() const -> int {
if (const auto* pattern = value_.dyn_cast<const Pattern*>()) {
if (const auto* tuple_pattern = dyn_cast<TuplePattern>(pattern)) {
return tuple_pattern->fields().size();
} else if (isa<AlternativePattern>(pattern)) {
return 1;
}
} else if (const auto* value = value_.dyn_cast<const Value*>()) {
if (const auto* tuple = dyn_cast<TupleValue>(value)) {
return tuple->elements().size();
} else if (isa<AlternativeValue>(value)) {
return 1;
}
}
return 0;
}
void AbstractPattern::AppendElementsTo(
std::vector<AbstractPattern>& out) const {
if (const auto* pattern = value_.dyn_cast<const Pattern*>()) {
if (const auto* tuple_pattern = dyn_cast<TuplePattern>(pattern)) {
auto fields = tuple_pattern->fields();
out.insert(out.end(), fields.begin(), fields.end());
} else if (const auto* alt_pattern =
dyn_cast<AlternativePattern>(pattern)) {
out.push_back(&alt_pattern->arguments());
}
} else if (const auto* value = value_.dyn_cast<const Value*>()) {
if (const auto* tuple = dyn_cast<TupleValue>(value)) {
const auto* tuple_type = cast<TupleType>(type_);
CARBON_CHECK(tuple->elements().size() == tuple_type->elements().size());
for (size_t i = 0; i != tuple->elements().size(); ++i) {
out.push_back(
AbstractPattern(tuple->elements()[i], tuple_type->elements()[i]));
}
} else if (const auto* alt = dyn_cast<AlternativeValue>(value)) {
if (auto arg = alt->argument()) {
out.push_back(AbstractPattern(
*arg, *alt->alternative().parameters_static_type()));
} else {
// There's no value to match for this alternative, so just insert a
// wildcard.
out.push_back(AbstractPattern::MakeWildcard());
}
}
}
}
auto AbstractPattern::value() const -> const Value& {
CARBON_CHECK(kind() == Primitive);
return *value_.get<const Value*>();
}
auto AbstractPattern::type() const -> const Value& {
CARBON_CHECK(kind() != Wildcard);
return *type_;
}
void AbstractPattern::Set(Nonnull<const Pattern*> pattern) {
type_ = &pattern->static_type();
switch (pattern->kind()) {
case PatternKind::AddrPattern:
case PatternKind::AutoPattern:
case PatternKind::BindingPattern:
case PatternKind::GenericBinding:
value_ = static_cast<const WildcardTag*>(nullptr);
break;
case PatternKind::TuplePattern:
case PatternKind::AlternativePattern:
value_ = pattern;
break;
case PatternKind::ExpressionPattern:
value_ = &pattern->value();
break;
case PatternKind::VarPattern:
Set(&cast<VarPattern>(pattern)->pattern());
break;
}
}
auto PatternMatrix::IsUseful(llvm::ArrayRef<AbstractPattern> pattern,
int max_exponential_depth) const -> bool {
if (matrix_.empty()) {
return true;
}
CARBON_CHECK(pattern.size() == matrix_[0].size());
if (matrix_[0].empty()) {
return false;
}
switch (pattern[0].kind()) {
case AbstractPattern::Wildcard: {
auto discrim = FirstColumnDiscriminators();
// Check if we hit the depth limit. If so, we act as if the
// constructors present in this position are not exhaustive, that is,
// as if the type we're matching has some other constructor not
// corresponding to anything written in the pattern in this position.
// This can lead us to conclude that a pattern is useful if it is not,
// and that a set of patterns is not exhaustive when it is.
int new_depth =
max_exponential_depth - (discrim.found.size() > 1 ? 1 : 0);
if (!discrim.any_missing && new_depth >= 0) {
for (auto found : discrim.found) {
if (Specialize(found).IsUseful(*SpecializeRow(pattern, found),
new_depth)) {
return true;
}
}
return false;
}
return Default().IsUseful(pattern.slice(1), max_exponential_depth);
}
case AbstractPattern::Compound: {
DiscriminatorInfo discrim = {.discriminator = pattern[0].discriminator(),
.size = pattern[0].elements_size()};
return Specialize(discrim).IsUseful(*SpecializeRow(pattern, discrim),
max_exponential_depth);
}
case AbstractPattern::Primitive: {
return Specialize(pattern[0].value())
.IsUseful(pattern.slice(1), max_exponential_depth);
}
}
}
auto PatternMatrix::FirstColumnDiscriminators() const -> DiscriminatorSet {
std::set<std::string_view> discrims;
std::optional<int> num_discrims;
std::optional<int> elem_size;
for (const auto& row : matrix_) {
CARBON_CHECK(!row.empty());
switch (row[0].kind()) {
case AbstractPattern::Wildcard:
continue;
case AbstractPattern::Compound: {
const Value& type = row[0].type();
if (const auto* tuple = dyn_cast<TupleType>(&type)) {
// If we find a tuple match, we've found all constructors (there's
// only one!) and none were missing.
return {
.found = {{.discriminator = {},
.size = static_cast<int>(tuple->elements().size())}},
.any_missing = false};
} else if (const auto* choice = dyn_cast<ChoiceType>(&type)) {
num_discrims = choice->declaration().alternatives().size();
elem_size = 1;
} else if (isa<BoolType>(type)) {
// `bool` behaves like a choice type with two alternatives,
// and with no nested patterns for either of them.
num_discrims = 2;
elem_size = 0;
} else {
llvm_unreachable("unexpected compound type");
}
discrims.insert(row[0].discriminator());
break;
}
case AbstractPattern::Primitive: {
// We assume that primitive value matches are always incomplete, even
// for types like `i8` where a covering match might be possible.
return {.found = {}, .any_missing = true};
}
}
}
if (!num_discrims || *num_discrims != static_cast<int>(discrims.size())) {
return {.found = {}, .any_missing = true};
}
DiscriminatorSet result = {.found = {}, .any_missing = false};
result.found.reserve(discrims.size());
for (auto s : discrims) {
result.found.push_back({.discriminator = s, .size = *elem_size});
}
return result;
}
auto PatternMatrix::SpecializeRow(llvm::ArrayRef<AbstractPattern> row,
DiscriminatorInfo discriminator)
-> std::optional<std::vector<AbstractPattern>> {
CARBON_CHECK(!row.empty());
std::vector<AbstractPattern> new_row;
switch (row[0].kind()) {
case AbstractPattern::Wildcard:
new_row.reserve(discriminator.size + row.size() - 1);
new_row.insert(new_row.end(), discriminator.size,
AbstractPattern::MakeWildcard());
break;
case AbstractPattern::Compound: {
if (row[0].discriminator() != discriminator.discriminator) {
return std::nullopt;
}
CARBON_CHECK(static_cast<int>(row[0].elements_size()) ==
discriminator.size);
new_row.reserve(discriminator.size + row.size() - 1);
row[0].AppendElementsTo(new_row);
break;
}
case AbstractPattern::Primitive:
// These cases should be rejected by the type checker.
llvm_unreachable("matched primitive against compound");
}
new_row.insert(new_row.end(), row.begin() + 1, row.end());
return std::move(new_row);
}
auto PatternMatrix::Specialize(DiscriminatorInfo discriminator) const
-> PatternMatrix {
PatternMatrix specialized;
for (const auto& row : matrix_) {
// TODO: If we add support for "or" patterns, specialization might
// produce multiple rows here.
if (auto new_row = SpecializeRow(row, discriminator)) {
specialized.Add(std::move(new_row.value()));
}
}
return specialized;
}
// Specialize the pattern matrix for the case where the first value is known
// to be `value`, and is not matched.
auto PatternMatrix::Specialize(const Value& value) const -> PatternMatrix {
PatternMatrix specialized;
for (const auto& row : matrix_) {
CARBON_CHECK(!row.empty());
switch (row[0].kind()) {
case AbstractPattern::Wildcard:
break;
case AbstractPattern::Compound:
llvm_unreachable("matched compound against primitive");
case AbstractPattern::Primitive:
// TODO: Use an equality context here?
if (!ValueEqual(&row[0].value(), &value, std::nullopt)) {
continue;
}
break;
}
specialized.Add(std::vector<AbstractPattern>(row.begin() + 1, row.end()));
}
return specialized;
}
// Specialize the pattern matrix for the case where the first value uses a
// discriminator matching none of the non-wildcard patterns.
auto PatternMatrix::Default() const -> PatternMatrix {
PatternMatrix default_matrix;
for (const auto& row : matrix_) {
CARBON_CHECK(!row.empty());
switch (row[0].kind()) {
case AbstractPattern::Wildcard:
default_matrix.Add(
std::vector<AbstractPattern>(row.begin() + 1, row.end()));
break;
case AbstractPattern::Compound:
case AbstractPattern::Primitive:
break;
}
}
return default_matrix;
}
} // namespace Carbon