// 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 using llvm::cast; using llvm::dyn_cast; using llvm::isa; namespace Carbon { auto AbstractPattern::kind() const -> Kind { if (isa(value_)) { return Compound; } if (const auto* value = value_.dyn_cast()) { if (isa(value)) { return Compound; } return Primitive; } CARBON_CHECK(isa(value_)); return Wildcard; } auto AbstractPattern::discriminator() const -> std::string_view { CARBON_CHECK(kind() == Compound); if (const auto* pattern = value_.dyn_cast()) { if (const auto* alt_pattern = dyn_cast(pattern)) { return alt_pattern->alternative_name(); } } else if (const auto* value = value_.dyn_cast()) { if (const auto* alt = dyn_cast(value)) { return alt->alternative().name(); } else if (const auto* bool_val = dyn_cast(value)) { return bool_val->value() ? "true" : "false"; } } return {}; } auto AbstractPattern::elements_size() const -> int { if (const auto* pattern = value_.dyn_cast()) { if (const auto* tuple_pattern = dyn_cast(pattern)) { return tuple_pattern->fields().size(); } else if (isa(pattern)) { return 1; } } else if (const auto* value = value_.dyn_cast()) { if (const auto* tuple = dyn_cast(value)) { return tuple->elements().size(); } else if (isa(value)) { return 1; } } return 0; } void AbstractPattern::AppendElementsTo( std::vector& out) const { if (const auto* pattern = value_.dyn_cast()) { if (const auto* tuple_pattern = dyn_cast(pattern)) { auto fields = tuple_pattern->fields(); out.insert(out.end(), fields.begin(), fields.end()); } else if (const auto* alt_pattern = dyn_cast(pattern)) { out.push_back(&alt_pattern->arguments()); } } else if (const auto* value = value_.dyn_cast()) { if (const auto* tuple = dyn_cast(value)) { const auto* tuple_type = cast(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(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 *cast(value_); } auto AbstractPattern::type() const -> const Value& { CARBON_CHECK(kind() != Wildcard); return *type_; } void AbstractPattern::Set(Nonnull pattern) { type_ = &pattern->static_type(); switch (pattern->kind()) { case PatternKind::AddrPattern: case PatternKind::AutoPattern: case PatternKind::BindingPattern: case PatternKind::GenericBinding: value_ = static_cast(nullptr); break; case PatternKind::TuplePattern: case PatternKind::AlternativePattern: value_ = pattern; break; case PatternKind::ExpressionPattern: value_ = &pattern->value(); break; case PatternKind::VarPattern: Set(&cast(pattern)->pattern()); break; } } auto PatternMatrix::IsUseful(llvm::ArrayRef 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 discrims; std::optional num_discrims; std::optional 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(&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(tuple->elements().size())}}, .any_missing = false}; } else if (const auto* choice = dyn_cast(&type)) { num_discrims = choice->declaration().alternatives().size(); elem_size = 1; } else if (isa(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(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 row, DiscriminatorInfo discriminator) -> std::optional> { CARBON_CHECK(!row.empty()); std::vector 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(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(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(row.begin() + 1, row.end())); break; case AbstractPattern::Compound: case AbstractPattern::Primitive: break; } } return default_matrix; } } // namespace Carbon