mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-27 17:10:09 +01:00
This switches `DCHECK` and `FATAL` as well. The goal is to reduce the code size impact of these assertions so that we can keep more of them enabled. Currently, the largest cost I see from `CHECK` is not the actual check or the cold code itself, but actually the failure to inline trivial functions due to the presence of the cold code. This means that our goal isn't to reduce apparent code size in the final binary but the LLVM IR cost assessed for these routines in the inliner, which closely correlates with code size but is a bit different. As discussed in #4283, experimentation shows that a single function call with a minimal number of arguments is the lowest cost model for these. This is easily achieved with a format-string API that internally uses `llvm::formatv`. This PR is essentially the `CHECK` version of #4283. However, the check macros are substantially harder to make work with both format strings and streaming because they also take a condition. Also, unexpectedly, I was very successful at devising a regular expression based automated rewrite from the streaming to the format string form with only low 10s of manual fixes. This includes compacting strings broken up across lines, etc. Given how well that went, I've prepared this PR which just directly switches to the format string API and migrate everything to use it. One nice side-effect is that the format string approach ends up greatly simplifying the implementation here as well. This is ... *shockingly* effective. Parsing speeds up by more than 3% with just this change. And checking speeds up by **8%** with this change alone: ``` BM_CompileAPIFileDenseDecls<Phase::Parse>/256 86.3µs ± 1% 82.9µs ± 1% -3.94% (p=0.000 n=17+19) BM_CompileAPIFileDenseDecls<Phase::Parse>/1024 431µs ± 1% 415µs ± 1% -3.76% (p=0.000 n=18+19) BM_CompileAPIFileDenseDecls<Phase::Parse>/4096 1.77ms ± 1% 1.71ms ± 1% -3.18% (p=0.000 n=18+19) BM_CompileAPIFileDenseDecls<Phase::Parse>/16384 7.44ms ± 1% 7.17ms ± 2% -3.56% (p=0.000 n=18+20) BM_CompileAPIFileDenseDecls<Phase::Parse>/65536 30.7ms ± 1% 29.7ms ± 1% -3.15% (p=0.000 n=18+20) BM_CompileAPIFileDenseDecls<Phase::Parse>/262144 131ms ± 1% 127ms ± 1% -2.81% (p=0.000 n=18+18) BM_CompileAPIFileDenseDecls<Phase::Check>/256 878µs ± 2% 800µs ± 1% -8.91% (p=0.000 n=19+20) BM_CompileAPIFileDenseDecls<Phase::Check>/1024 1.88ms ± 2% 1.72ms ± 1% -8.56% (p=0.000 n=19+20) BM_CompileAPIFileDenseDecls<Phase::Check>/4096 5.78ms ± 2% 5.28ms ± 1% -8.70% (p=0.000 n=20+18) BM_CompileAPIFileDenseDecls<Phase::Check>/16384 21.9ms ± 1% 20.1ms ± 1% -8.02% (p=0.000 n=18+20) BM_CompileAPIFileDenseDecls<Phase::Check>/65536 90.4ms ± 2% 83.1ms ± 1% -8.04% (p=0.000 n=19+20) BM_CompileAPIFileDenseDecls<Phase::Check>/262144 381ms ± 2% 352ms ± 1% -7.79% (p=0.000 n=19+19) ``` --------- Co-authored-by: Richard Smith <richard@metafoo.co.uk> Co-authored-by: josh11b <15258583+josh11b@users.noreply.github.com>
1371 lines
49 KiB
C++
1371 lines
49 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/ast/value.h"
|
|
|
|
#include <algorithm>
|
|
#include <optional>
|
|
#include <string_view>
|
|
|
|
#include "common/check.h"
|
|
#include "common/error.h"
|
|
#include "explorer/ast/declaration.h"
|
|
#include "explorer/ast/element.h"
|
|
#include "explorer/ast/element_path.h"
|
|
#include "explorer/ast/value_transform.h"
|
|
#include "explorer/base/arena.h"
|
|
#include "explorer/base/error_builders.h"
|
|
#include "llvm/ADT/STLExtras.h"
|
|
#include "llvm/ADT/StringExtras.h"
|
|
#include "llvm/Support/Casting.h"
|
|
|
|
namespace Carbon {
|
|
|
|
using llvm::cast;
|
|
using llvm::dyn_cast;
|
|
using llvm::dyn_cast_or_null;
|
|
using llvm::isa;
|
|
|
|
namespace {
|
|
// A visitor that walks the Value*s nested within a value.
|
|
struct NestedValueVisitor {
|
|
template <typename T>
|
|
auto VisitParts(const T& decomposable) -> bool {
|
|
return decomposable.Decompose(
|
|
[&](const auto&... parts) { return (Visit(parts) && ...); });
|
|
}
|
|
|
|
auto Visit(Nonnull<const Value*> value) -> bool {
|
|
if (!callback(value)) {
|
|
return false;
|
|
}
|
|
|
|
return value->Visit<bool>(
|
|
[&](const auto* derived_value) { return VisitParts(*derived_value); });
|
|
}
|
|
|
|
auto Visit(Nonnull<const Bindings*> bindings) -> bool {
|
|
for (auto [binding, value] : bindings->args()) {
|
|
if (!Visit(value)) {
|
|
return false;
|
|
}
|
|
}
|
|
for (auto [binding, value] : bindings->witnesses()) {
|
|
if (!Visit(value)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
template <typename T>
|
|
auto Visit(const std::vector<T>& vec) -> bool {
|
|
for (auto& v : vec) {
|
|
if (!Visit(v)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
template <typename T>
|
|
auto Visit(const std::optional<T>& opt) -> bool {
|
|
return !opt || Visit(*opt);
|
|
}
|
|
|
|
template <typename T,
|
|
typename = std::enable_if_t<IsRecursivelyTransformable<T>>>
|
|
auto Visit(Nonnull<const T*> value) -> bool {
|
|
return VisitParts(*value);
|
|
}
|
|
template <typename T,
|
|
typename = std::enable_if_t<IsRecursivelyTransformable<T>>>
|
|
auto Visit(const T& value) -> bool {
|
|
return VisitParts(value);
|
|
}
|
|
|
|
// Other value components can't refer to a value.
|
|
auto Visit(Nonnull<const AstNode*>) -> bool { return true; }
|
|
auto Visit(ValueNodeView) -> bool { return true; }
|
|
auto Visit(int) -> bool { return true; }
|
|
auto Visit(Address) -> bool { return true; }
|
|
auto Visit(ExpressionCategory) -> bool { return true; }
|
|
auto Visit(const std::string&) -> bool { return true; }
|
|
auto Visit(Nonnull<const NominalClassValue**>) -> bool {
|
|
// This is the pointer to the most-derived value within a class value,
|
|
// which is not "within" this value, so we shouldn't visit it.
|
|
return true;
|
|
}
|
|
auto Visit(const VTable*) -> bool { return true; }
|
|
|
|
llvm::function_ref<bool(const Value*)> callback;
|
|
};
|
|
} // namespace
|
|
|
|
auto VisitNestedValues(Nonnull<const Value*> value,
|
|
llvm::function_ref<bool(const Value*)> visitor) -> bool {
|
|
return NestedValueVisitor{.callback = visitor}.Visit(value);
|
|
}
|
|
|
|
auto StructValue::FindField(std::string_view name) const
|
|
-> std::optional<Nonnull<const Value*>> {
|
|
for (const NamedValue& element : elements_) {
|
|
if (element.name == name) {
|
|
return element.value;
|
|
}
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
NominalClassValue::NominalClassValue(
|
|
Nonnull<const Value*> type, Nonnull<const Value*> inits,
|
|
std::optional<Nonnull<const NominalClassValue*>> base,
|
|
Nonnull<const NominalClassValue** const> class_value_ptr)
|
|
: Value(Kind::NominalClassValue),
|
|
type_(type),
|
|
inits_(inits),
|
|
base_(base),
|
|
class_value_ptr_(class_value_ptr) {
|
|
CARBON_CHECK(!base || (*base)->class_value_ptr() == class_value_ptr);
|
|
// Update ancestors's class value to point to latest child.
|
|
*class_value_ptr_ = this;
|
|
}
|
|
|
|
static auto FindClassField(Nonnull<const NominalClassValue*> object,
|
|
std::string_view name)
|
|
-> std::optional<Nonnull<const Value*>> {
|
|
if (auto field = cast<StructValue>(object->inits()).FindField(name)) {
|
|
return field;
|
|
}
|
|
if (object->base().has_value()) {
|
|
return FindClassField(object->base().value(), name);
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
static auto GetBaseElement(Nonnull<const NominalClassValue*> class_value,
|
|
SourceLocation source_loc)
|
|
-> ErrorOr<Nonnull<const Value*>> {
|
|
const auto base = cast<NominalClassValue>(class_value)->base();
|
|
if (!base.has_value()) {
|
|
return ProgramError(source_loc)
|
|
<< "Non-existent base class for " << *class_value;
|
|
}
|
|
return base.value();
|
|
}
|
|
|
|
static auto GetPositionalElement(Nonnull<const TupleValue*> tuple,
|
|
const ElementPath::Component& path_comp,
|
|
SourceLocation source_loc)
|
|
-> ErrorOr<Nonnull<const Value*>> {
|
|
CARBON_CHECK(path_comp.element()->kind() == ElementKind::PositionalElement,
|
|
"Invalid non-tuple member");
|
|
const auto* tuple_element = cast<PositionalElement>(path_comp.element());
|
|
const size_t index = tuple_element->index();
|
|
if (index < 0 || index >= tuple->elements().size()) {
|
|
return ProgramError(source_loc)
|
|
<< "index " << index << " out of range for " << *tuple;
|
|
}
|
|
return tuple->elements()[index];
|
|
}
|
|
|
|
static auto GetNamedElement(Nonnull<Arena*> arena, Nonnull<const Value*> v,
|
|
const ElementPath::Component& field,
|
|
SourceLocation source_loc,
|
|
std::optional<Nonnull<const Value*>> me_value)
|
|
-> ErrorOr<Nonnull<const Value*>> {
|
|
CARBON_CHECK(field.element()->kind() == ElementKind::NamedElement,
|
|
"Invalid element, expecting NamedElement");
|
|
const auto* member = cast<NamedElement>(field.element());
|
|
const auto f = member->name();
|
|
if (field.witness().has_value()) {
|
|
const auto* witness = cast<Witness>(*field.witness());
|
|
|
|
// Associated constants.
|
|
if (const auto* assoc_const =
|
|
dyn_cast_or_null<AssociatedConstantDeclaration>(
|
|
member->declaration().value_or(nullptr))) {
|
|
CARBON_CHECK(field.interface(), "have witness but no interface");
|
|
// TODO: Use witness to find the value of the constant.
|
|
return arena->New<AssociatedConstant>(v, *field.interface(), assoc_const,
|
|
witness);
|
|
}
|
|
|
|
// Associated functions.
|
|
if (const auto* impl_witness = dyn_cast<ImplWitness>(witness)) {
|
|
if (std::optional<Nonnull<const Declaration*>> mem_decl =
|
|
FindMember(f, impl_witness->declaration().members());
|
|
mem_decl.has_value()) {
|
|
const auto& fun_decl = cast<FunctionDeclaration>(**mem_decl);
|
|
if (fun_decl.is_method()) {
|
|
return arena->New<BoundMethodValue>(&fun_decl, *me_value,
|
|
&impl_witness->bindings());
|
|
} else {
|
|
// Class function.
|
|
const auto* fun = cast<FunctionValue>(*fun_decl.constant_value());
|
|
return arena->New<FunctionValue>(&fun->declaration(),
|
|
&impl_witness->bindings());
|
|
}
|
|
} else {
|
|
return ProgramError(source_loc)
|
|
<< "member " << f << " not in " << *witness;
|
|
}
|
|
} else {
|
|
return ProgramError(source_loc)
|
|
<< "member lookup for " << f << " in symbolic " << *witness;
|
|
}
|
|
}
|
|
switch (v->kind()) {
|
|
case Value::Kind::StructValue: {
|
|
std::optional<Nonnull<const Value*>> field =
|
|
cast<StructValue>(*v).FindField(f);
|
|
if (field == std::nullopt) {
|
|
return ProgramError(source_loc) << "member " << f << " not in " << *v;
|
|
}
|
|
return *field;
|
|
}
|
|
case Value::Kind::NominalClassValue: {
|
|
const auto& object = cast<NominalClassValue>(*v);
|
|
// Look for a field.
|
|
if (std::optional<Nonnull<const Value*>> field =
|
|
FindClassField(&object, f)) {
|
|
return *field;
|
|
} else {
|
|
// Look for a method in the object's class
|
|
const auto& class_type = cast<NominalClassType>(object.type());
|
|
std::optional<Nonnull<const FunctionValue*>> func =
|
|
FindFunctionWithParents(f, class_type.declaration());
|
|
if (!func) {
|
|
return ProgramError(source_loc) << "member " << f << " not in " << *v
|
|
<< " or its " << class_type;
|
|
} else if ((*func)->declaration().is_method()) {
|
|
// Found a method. Turn it into a bound method.
|
|
const auto& m = cast<FunctionValue>(**func);
|
|
if (m.declaration().virt_override() == VirtualOverride::None) {
|
|
return arena->New<BoundMethodValue>(&m.declaration(), *me_value,
|
|
&class_type.bindings());
|
|
}
|
|
// Method is virtual, get child-most class value and perform vtable
|
|
// lookup.
|
|
const auto& last_child_value = **object.class_value_ptr();
|
|
const auto& last_child_type =
|
|
cast<NominalClassType>(last_child_value.type());
|
|
const auto res = last_child_type.vtable().find(f);
|
|
CARBON_CHECK(res != last_child_type.vtable().end());
|
|
const auto [virtual_method, level] = res->second;
|
|
const auto level_diff = last_child_type.hierarchy_level() - level;
|
|
const auto* m_class_value = &last_child_value;
|
|
// Get class value matching the virtual method, and turn it into a
|
|
// bound method.
|
|
for (int i = 0; i < level_diff; ++i) {
|
|
CARBON_CHECK(m_class_value->base(),
|
|
"Error trying to access function class value");
|
|
m_class_value = *m_class_value->base();
|
|
}
|
|
return arena->New<BoundMethodValue>(
|
|
cast<FunctionDeclaration>(virtual_method), m_class_value,
|
|
&class_type.bindings());
|
|
} else {
|
|
// Found a class function
|
|
// TODO: This should not be reachable.
|
|
return arena->New<FunctionValue>(&(*func)->declaration(),
|
|
&class_type.bindings());
|
|
}
|
|
}
|
|
}
|
|
case Value::Kind::ChoiceType: {
|
|
const auto& choice = cast<ChoiceType>(*v);
|
|
auto alt = choice.declaration().FindAlternative(f);
|
|
if (!alt) {
|
|
return ProgramError(source_loc)
|
|
<< "alternative " << f << " not in " << *v;
|
|
}
|
|
if ((*alt)->parameters()) {
|
|
return arena->New<AlternativeConstructorValue>(&choice, *alt);
|
|
}
|
|
return arena->New<AlternativeValue>(&choice, *alt, std::nullopt);
|
|
}
|
|
case Value::Kind::NominalClassType: {
|
|
// Access a class function.
|
|
const auto& class_type = cast<NominalClassType>(*v);
|
|
std::optional<Nonnull<const FunctionValue*>> fun =
|
|
FindFunctionWithParents(f, class_type.declaration());
|
|
if (fun == std::nullopt) {
|
|
return ProgramError(source_loc)
|
|
<< "class function " << f << " not in " << *v;
|
|
}
|
|
return arena->New<FunctionValue>(&(*fun)->declaration(),
|
|
&class_type.bindings());
|
|
}
|
|
default:
|
|
CARBON_FATAL("named element access not supported for value {0}", *v);
|
|
}
|
|
}
|
|
|
|
static auto GetElement(Nonnull<Arena*> arena, Nonnull<const Value*> v,
|
|
const ElementPath::Component& path_comp,
|
|
SourceLocation source_loc,
|
|
std::optional<Nonnull<const Value*>> me_value)
|
|
-> ErrorOr<Nonnull<const Value*>> {
|
|
switch (path_comp.element()->kind()) {
|
|
case ElementKind::NamedElement:
|
|
return GetNamedElement(arena, v, path_comp, source_loc, me_value);
|
|
case ElementKind::PositionalElement: {
|
|
if (const auto* tuple = dyn_cast<TupleValue>(v)) {
|
|
return GetPositionalElement(tuple, path_comp, source_loc);
|
|
} else {
|
|
CARBON_FATAL("Invalid value for positional element");
|
|
}
|
|
}
|
|
case ElementKind::BaseElement:
|
|
switch (v->kind()) {
|
|
case Value::Kind::NominalClassValue:
|
|
return GetBaseElement(cast<NominalClassValue>(v), source_loc);
|
|
case Value::Kind::PointerValue: {
|
|
const auto* ptr = cast<PointerValue>(v);
|
|
return arena->New<PointerValue>(
|
|
ptr->address().ElementAddress(path_comp.element()));
|
|
}
|
|
default:
|
|
CARBON_FATAL("Invalid value for base element");
|
|
}
|
|
}
|
|
}
|
|
|
|
auto Value::GetElement(Nonnull<Arena*> arena, const ElementPath& path,
|
|
SourceLocation source_loc,
|
|
std::optional<Nonnull<const Value*>> me_value) const
|
|
-> ErrorOr<Nonnull<const Value*>> {
|
|
Nonnull<const Value*> value(this);
|
|
for (const ElementPath::Component& field : path.components_) {
|
|
CARBON_ASSIGN_OR_RETURN(
|
|
value, Carbon::GetElement(arena, value, field, source_loc, me_value));
|
|
}
|
|
return value;
|
|
}
|
|
|
|
static auto SetFieldImpl(
|
|
Nonnull<Arena*> arena, Nonnull<const Value*> value,
|
|
std::vector<ElementPath::Component>::const_iterator path_begin,
|
|
std::vector<ElementPath::Component>::const_iterator path_end,
|
|
Nonnull<const Value*> field_value, SourceLocation source_loc)
|
|
-> ErrorOr<Nonnull<const Value*>> {
|
|
if (path_begin == path_end) {
|
|
return field_value;
|
|
}
|
|
switch (value->kind()) {
|
|
case Value::Kind::StructValue: {
|
|
std::vector<NamedValue> elements = cast<StructValue>(*value).elements();
|
|
auto it =
|
|
llvm::find_if(elements, [path_begin](const NamedValue& element) {
|
|
return (*path_begin).IsNamed(element.name);
|
|
});
|
|
if (it == elements.end()) {
|
|
return ProgramError(source_loc)
|
|
<< "field " << *path_begin << " not in " << *value;
|
|
}
|
|
CARBON_ASSIGN_OR_RETURN(
|
|
it->value, SetFieldImpl(arena, it->value, path_begin + 1, path_end,
|
|
field_value, source_loc));
|
|
return arena->New<StructValue>(elements);
|
|
}
|
|
case Value::Kind::NominalClassValue: {
|
|
const auto& object = cast<NominalClassValue>(*value);
|
|
if (auto inits = SetFieldImpl(arena, &object.inits(), path_begin,
|
|
path_end, field_value, source_loc);
|
|
inits.ok()) {
|
|
auto* class_value_ptr = arena->New<const NominalClassValue*>();
|
|
std::vector<const NominalClassValue*> base_path;
|
|
for (auto base = object.base(); base; base = (*base)->base()) {
|
|
base_path.push_back(*base);
|
|
}
|
|
std::optional<Nonnull<const NominalClassValue*>> base;
|
|
for (auto* base_path_elem : llvm::reverse(base_path)) {
|
|
base = arena->New<NominalClassValue>(&base_path_elem->type(),
|
|
&base_path_elem->inits(), base,
|
|
class_value_ptr);
|
|
}
|
|
return arena->New<NominalClassValue>(&object.type(), *inits, base,
|
|
class_value_ptr);
|
|
} else if (object.base().has_value()) {
|
|
auto new_base = SetFieldImpl(arena, object.base().value(), path_begin,
|
|
path_end, field_value, source_loc);
|
|
if (new_base.ok()) {
|
|
auto as_nominal_class_value = cast<NominalClassValue>(*new_base);
|
|
return arena->New<NominalClassValue>(
|
|
&object.type(), &object.inits(), as_nominal_class_value,
|
|
as_nominal_class_value->class_value_ptr());
|
|
}
|
|
}
|
|
// Failed to match, show full object content
|
|
return ProgramError(source_loc)
|
|
<< "field " << *path_begin << " not in " << *value;
|
|
}
|
|
case Value::Kind::TupleType:
|
|
case Value::Kind::TupleValue: {
|
|
CARBON_CHECK(
|
|
(*path_begin).element()->kind() == ElementKind::PositionalElement,
|
|
"Invalid non-positional member for tuple");
|
|
std::vector<Nonnull<const Value*>> elements =
|
|
cast<TupleValueBase>(*value).elements();
|
|
const size_t index =
|
|
cast<PositionalElement>((*path_begin).element())->index();
|
|
if (index < 0 || index >= elements.size()) {
|
|
return ProgramError(source_loc)
|
|
<< "index " << index << " out of range in " << *value;
|
|
}
|
|
CARBON_ASSIGN_OR_RETURN(
|
|
elements[index], SetFieldImpl(arena, elements[index], path_begin + 1,
|
|
path_end, field_value, source_loc));
|
|
if (isa<TupleType>(value)) {
|
|
return arena->New<TupleType>(elements);
|
|
} else {
|
|
return arena->New<TupleValue>(elements);
|
|
}
|
|
}
|
|
default:
|
|
CARBON_FATAL("field access not allowed for value {0}", *value);
|
|
}
|
|
}
|
|
|
|
auto Value::SetField(Nonnull<Arena*> arena, const ElementPath& path,
|
|
Nonnull<const Value*> field_value,
|
|
SourceLocation source_loc) const
|
|
-> ErrorOr<Nonnull<const Value*>> {
|
|
return SetFieldImpl(arena, static_cast<Nonnull<const Value*>>(this),
|
|
path.components_.begin(), path.components_.end(),
|
|
field_value, source_loc);
|
|
}
|
|
|
|
static auto PrintNameWithBindings(llvm::raw_ostream& out,
|
|
Nonnull<const Declaration*> declaration,
|
|
const BindingMap& args) {
|
|
out << GetName(*declaration).value_or("(anonymous)");
|
|
// TODO: Print '()' if declaration is parameterized but no args are provided.
|
|
if (!args.empty()) {
|
|
out << "(";
|
|
llvm::ListSeparator sep;
|
|
for (const auto& [bind, val] : args) {
|
|
out << sep << bind->name() << " = " << *val;
|
|
}
|
|
out << ")";
|
|
}
|
|
}
|
|
|
|
void Value::Print(llvm::raw_ostream& out) const {
|
|
switch (kind()) {
|
|
case Value::Kind::AlternativeConstructorValue: {
|
|
const auto& alt = cast<AlternativeConstructorValue>(*this);
|
|
out << alt.choice().declaration().name() << "."
|
|
<< alt.alternative().name();
|
|
break;
|
|
}
|
|
case Value::Kind::BindingPlaceholderValue: {
|
|
const auto& placeholder = cast<BindingPlaceholderValue>(*this);
|
|
out << "Placeholder<";
|
|
if (placeholder.value_node().has_value()) {
|
|
out << (*placeholder.value_node());
|
|
} else {
|
|
out << "_";
|
|
}
|
|
out << ">";
|
|
break;
|
|
}
|
|
case Value::Kind::AddrValue: {
|
|
const auto& addr = cast<AddrValue>(*this);
|
|
out << "Addr<" << addr.pattern() << ">";
|
|
break;
|
|
}
|
|
case Value::Kind::AlternativeValue: {
|
|
const auto& alt = cast<AlternativeValue>(*this);
|
|
out << alt.choice().declaration().name() << "."
|
|
<< alt.alternative().name();
|
|
if (auto arg = alt.argument()) {
|
|
out << **arg;
|
|
}
|
|
break;
|
|
}
|
|
case Value::Kind::StructValue: {
|
|
const auto& struct_val = cast<StructValue>(*this);
|
|
out << "{";
|
|
llvm::ListSeparator sep;
|
|
for (const NamedValue& element : struct_val.elements()) {
|
|
out << sep << "." << element.name << " = " << *element.value;
|
|
}
|
|
out << "}";
|
|
break;
|
|
}
|
|
case Value::Kind::NominalClassValue: {
|
|
const auto& s = cast<NominalClassValue>(*this);
|
|
out << cast<NominalClassType>(s.type()).declaration().name() << s.inits();
|
|
if (s.base().has_value()) {
|
|
out << " base " << *s.base().value();
|
|
}
|
|
break;
|
|
}
|
|
case Value::Kind::TupleType:
|
|
case Value::Kind::TupleValue: {
|
|
out << "(";
|
|
llvm::ListSeparator sep;
|
|
const auto elements = cast<TupleValueBase>(*this).elements();
|
|
for (Nonnull<const Value*> element : elements) {
|
|
out << sep << *element;
|
|
}
|
|
// Print trailing comma for single element tuples: (i32,).
|
|
if (elements.size() == 1) {
|
|
out << ",";
|
|
}
|
|
out << ")";
|
|
break;
|
|
}
|
|
case Value::Kind::IntValue:
|
|
out << cast<IntValue>(*this).value();
|
|
break;
|
|
case Value::Kind::BoolValue:
|
|
out << (cast<BoolValue>(*this).value() ? "true" : "false");
|
|
break;
|
|
case Value::Kind::DestructorValue: {
|
|
const auto& destructor = cast<DestructorValue>(*this);
|
|
out << "destructor [ ";
|
|
out << destructor.declaration().self_pattern();
|
|
out << " ]";
|
|
break;
|
|
}
|
|
case Value::Kind::FunctionValue: {
|
|
const auto& fun = cast<FunctionValue>(*this);
|
|
out << "fun<" << fun.declaration().name() << ">";
|
|
if (!fun.type_args().empty()) {
|
|
out << "[";
|
|
llvm::ListSeparator sep;
|
|
for (const auto& [ty_var, ty_arg] : fun.type_args()) {
|
|
out << sep << *ty_var << "=" << *ty_arg;
|
|
}
|
|
out << "]";
|
|
}
|
|
if (!fun.witnesses().empty()) {
|
|
out << "{|";
|
|
llvm::ListSeparator sep;
|
|
for (const auto& [impl_bind, witness] : fun.witnesses()) {
|
|
out << sep << *witness;
|
|
}
|
|
out << "|}";
|
|
}
|
|
break;
|
|
}
|
|
case Value::Kind::BoundMethodValue: {
|
|
const auto& method = cast<BoundMethodValue>(*this);
|
|
out << "bound_method<" << method.declaration().name() << ">";
|
|
if (!method.type_args().empty()) {
|
|
out << "[";
|
|
llvm::ListSeparator sep;
|
|
for (const auto& [ty_var, ty_arg] : method.type_args()) {
|
|
out << sep << *ty_var << "=" << *ty_arg;
|
|
}
|
|
out << "]";
|
|
}
|
|
if (!method.witnesses().empty()) {
|
|
out << "{|";
|
|
llvm::ListSeparator sep;
|
|
for (const auto& [impl_bind, witness] : method.witnesses()) {
|
|
out << sep << *witness;
|
|
}
|
|
out << "|}";
|
|
}
|
|
break;
|
|
}
|
|
case Value::Kind::PointerValue:
|
|
out << "ptr<" << cast<PointerValue>(*this).address() << ">";
|
|
break;
|
|
case Value::Kind::LocationValue:
|
|
out << "lval<" << cast<LocationValue>(*this).address() << ">";
|
|
break;
|
|
case Value::Kind::ReferenceExpressionValue:
|
|
out << "ref_expr<" << cast<ReferenceExpressionValue>(*this).address()
|
|
<< ">";
|
|
break;
|
|
case Value::Kind::BoolType:
|
|
out << "bool";
|
|
break;
|
|
case Value::Kind::IntType:
|
|
out << "i32";
|
|
break;
|
|
case Value::Kind::TypeType:
|
|
out << "type";
|
|
break;
|
|
case Value::Kind::AutoType:
|
|
out << "auto";
|
|
break;
|
|
case Value::Kind::PointerType:
|
|
out << cast<PointerType>(*this).pointee_type() << "*";
|
|
break;
|
|
case Value::Kind::FunctionType: {
|
|
const auto& fn_type = cast<FunctionType>(*this);
|
|
out << "fn ";
|
|
auto self = fn_type.method_self();
|
|
if (!fn_type.deduced_bindings().empty() || self.has_value()) {
|
|
out << "[";
|
|
llvm::ListSeparator sep;
|
|
for (Nonnull<const GenericBinding*> deduced :
|
|
fn_type.deduced_bindings()) {
|
|
out << sep << *deduced;
|
|
}
|
|
if (self.has_value()) {
|
|
if (self->addr_self) {
|
|
out << sep << "addr self: " << *self->self_type << "*";
|
|
} else {
|
|
out << sep << "self: " << *self->self_type;
|
|
}
|
|
}
|
|
out << "]";
|
|
}
|
|
out << fn_type.parameters() << " -> " << fn_type.return_type();
|
|
break;
|
|
}
|
|
case Value::Kind::StructType: {
|
|
out << "{";
|
|
llvm::ListSeparator sep;
|
|
for (const auto& [name, type] : cast<StructType>(*this).fields()) {
|
|
out << sep << "." << name << ": " << *type;
|
|
}
|
|
out << "}";
|
|
break;
|
|
}
|
|
case Value::Kind::UninitializedValue: {
|
|
const auto& uninit = cast<UninitializedValue>(*this);
|
|
out << "Uninit<" << uninit.pattern() << ">";
|
|
break;
|
|
}
|
|
case Value::Kind::NominalClassType: {
|
|
const auto& class_type = cast<NominalClassType>(*this);
|
|
out << "class ";
|
|
PrintNameWithBindings(out, &class_type.declaration(),
|
|
class_type.type_args());
|
|
if (!class_type.witnesses().empty()) {
|
|
out << " witnesses ";
|
|
llvm::ListSeparator sep;
|
|
for (const auto& [impl_bind, witness] : class_type.witnesses()) {
|
|
out << sep << *witness;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case Value::Kind::ChoiceType: {
|
|
const auto& choice_type = cast<ChoiceType>(*this);
|
|
out << "choice ";
|
|
PrintNameWithBindings(out, &choice_type.declaration(),
|
|
choice_type.type_args());
|
|
break;
|
|
}
|
|
case Value::Kind::MixinPseudoType: {
|
|
const auto& mixin_type = cast<MixinPseudoType>(*this);
|
|
out << "mixin ";
|
|
PrintNameWithBindings(out, &mixin_type.declaration(), mixin_type.args());
|
|
if (!mixin_type.witnesses().empty()) {
|
|
out << " witnesses ";
|
|
llvm::ListSeparator sep;
|
|
for (const auto& [impl_bind, witness] : mixin_type.witnesses()) {
|
|
out << sep << *witness;
|
|
}
|
|
}
|
|
// TODO: print the import interface
|
|
break;
|
|
}
|
|
case Value::Kind::InterfaceType: {
|
|
const auto& iface_type = cast<InterfaceType>(*this);
|
|
out << "interface ";
|
|
PrintNameWithBindings(out, &iface_type.declaration(),
|
|
iface_type.bindings().args());
|
|
break;
|
|
}
|
|
case Value::Kind::NamedConstraintType: {
|
|
const auto& constraint_type = cast<NamedConstraintType>(*this);
|
|
out << "constraint ";
|
|
PrintNameWithBindings(out, &constraint_type.declaration(),
|
|
constraint_type.bindings().args());
|
|
break;
|
|
}
|
|
case Value::Kind::ConstraintType: {
|
|
const auto& constraint = cast<ConstraintType>(*this);
|
|
llvm::ListSeparator combine(" & ");
|
|
for (const LookupContext& ctx : constraint.lookup_contexts()) {
|
|
out << combine << *ctx.context;
|
|
}
|
|
if (constraint.lookup_contexts().empty()) {
|
|
out << "type";
|
|
}
|
|
out << " where ";
|
|
llvm::ListSeparator sep(" and ");
|
|
for (const RewriteConstraint& rewrite :
|
|
constraint.rewrite_constraints()) {
|
|
out << sep << ".(";
|
|
PrintNameWithBindings(out, &rewrite.constant->interface().declaration(),
|
|
rewrite.constant->interface().args());
|
|
out << "." << *GetName(rewrite.constant->constant())
|
|
<< ") = " << *rewrite.unconverted_replacement;
|
|
}
|
|
for (const ImplsConstraint& impl : constraint.impls_constraints()) {
|
|
// TODO: Skip cases where `impl.type` is `.Self` and the interface is
|
|
// in `lookup_contexts()`.
|
|
out << sep << *impl.type << " impls " << *impl.interface;
|
|
}
|
|
for (const EqualityConstraint& equality :
|
|
constraint.equality_constraints()) {
|
|
out << sep;
|
|
llvm::ListSeparator equal(" == ");
|
|
for (Nonnull<const Value*> value : equality.values) {
|
|
out << equal << *value;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case Value::Kind::ImplWitness: {
|
|
const auto& witness = cast<ImplWitness>(*this);
|
|
out << "witness for impl " << *witness.declaration().impl_type() << " as "
|
|
<< witness.declaration().interface();
|
|
break;
|
|
}
|
|
case Value::Kind::BindingWitness: {
|
|
const auto& witness = cast<BindingWitness>(*this);
|
|
out << "witness for " << *witness.binding()->type_var();
|
|
break;
|
|
}
|
|
case Value::Kind::ConstraintWitness: {
|
|
const auto& witness = cast<ConstraintWitness>(*this);
|
|
out << "(";
|
|
llvm::ListSeparator sep;
|
|
for (const auto* elem : witness.witnesses()) {
|
|
out << sep << *elem;
|
|
}
|
|
out << ")";
|
|
break;
|
|
}
|
|
case Value::Kind::ConstraintImplWitness: {
|
|
const auto& witness = cast<ConstraintImplWitness>(*this);
|
|
out << "witness " << witness.index() << " of "
|
|
<< *witness.constraint_witness();
|
|
break;
|
|
}
|
|
case Value::Kind::ParameterizedEntityName:
|
|
out << *GetName(cast<ParameterizedEntityName>(*this).declaration());
|
|
break;
|
|
case Value::Kind::MemberName: {
|
|
const auto& member_name = cast<MemberName>(*this);
|
|
if (member_name.base_type().has_value()) {
|
|
out << *member_name.base_type().value();
|
|
}
|
|
if (member_name.base_type().has_value() &&
|
|
member_name.interface().has_value()) {
|
|
out << "(";
|
|
}
|
|
if (member_name.interface().has_value()) {
|
|
out << *member_name.interface().value();
|
|
}
|
|
out << "." << member_name.member();
|
|
if (member_name.base_type().has_value() &&
|
|
member_name.interface().has_value()) {
|
|
out << ")";
|
|
}
|
|
break;
|
|
}
|
|
case Value::Kind::VariableType:
|
|
out << cast<VariableType>(*this).binding().name();
|
|
break;
|
|
case Value::Kind::AssociatedConstant: {
|
|
const auto& assoc = cast<AssociatedConstant>(*this);
|
|
out << "(" << assoc.base() << ").(";
|
|
PrintNameWithBindings(out, &assoc.interface().declaration(),
|
|
assoc.interface().args());
|
|
out << "." << *GetName(assoc.constant()) << ")";
|
|
break;
|
|
}
|
|
case Value::Kind::StringType:
|
|
out << "String";
|
|
break;
|
|
case Value::Kind::StringValue:
|
|
out << "\"";
|
|
out.write_escaped(cast<StringValue>(*this).value());
|
|
out << "\"";
|
|
break;
|
|
case Value::Kind::TypeOfMixinPseudoType:
|
|
out << "typeof("
|
|
<< cast<TypeOfMixinPseudoType>(*this)
|
|
.mixin_type()
|
|
.declaration()
|
|
.name()
|
|
<< ")";
|
|
break;
|
|
case Value::Kind::TypeOfParameterizedEntityName:
|
|
out << "parameterized entity name "
|
|
<< cast<TypeOfParameterizedEntityName>(*this).name();
|
|
break;
|
|
case Value::Kind::TypeOfMemberName: {
|
|
out << "member name " << cast<TypeOfMemberName>(*this).member();
|
|
break;
|
|
}
|
|
case Value::Kind::TypeOfNamespaceName: {
|
|
cast<TypeOfNamespaceName>(*this).namespace_decl()->PrintID(out);
|
|
break;
|
|
}
|
|
case Value::Kind::StaticArrayType: {
|
|
const auto& array_type = cast<StaticArrayType>(*this);
|
|
out << "[" << array_type.element_type() << ";";
|
|
if (array_type.has_size()) {
|
|
out << " " << array_type.size();
|
|
}
|
|
out << "]";
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void IntrinsicConstraint::Print(llvm::raw_ostream& out) const {
|
|
out << *type << " is ";
|
|
switch (kind) {
|
|
case IntrinsicConstraint::ImplicitAs:
|
|
out << "__intrinsic_implicit_as";
|
|
break;
|
|
}
|
|
if (!arguments.empty()) {
|
|
out << "(";
|
|
llvm::ListSeparator comma;
|
|
for (Nonnull<const Value*> argument : arguments) {
|
|
out << comma << *argument;
|
|
}
|
|
out << ")";
|
|
}
|
|
}
|
|
|
|
// Check whether two binding maps, which are assumed to have the same keys, are
|
|
// equal.
|
|
static auto BindingMapEqual(
|
|
const BindingMap& map1, const BindingMap& map2,
|
|
std::optional<Nonnull<const EqualityContext*>> equality_ctx) -> bool {
|
|
CARBON_CHECK(map1.size() == map2.size(), "maps should have same keys");
|
|
for (const auto& [key, value] : map1) {
|
|
if (!ValueEqual(value, map2.at(key), equality_ctx)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
auto TypeEqual(Nonnull<const Value*> t1, Nonnull<const Value*> t2,
|
|
std::optional<Nonnull<const EqualityContext*>> equality_ctx)
|
|
-> bool {
|
|
if (t1 == t2) {
|
|
return true;
|
|
}
|
|
if (t1->kind() != t2->kind()) {
|
|
if (IsValueKindDependent(t1) || IsValueKindDependent(t2)) {
|
|
return ValueEqual(t1, t2, equality_ctx);
|
|
}
|
|
return false;
|
|
}
|
|
switch (t1->kind()) {
|
|
case Value::Kind::PointerType:
|
|
return TypeEqual(&cast<PointerType>(*t1).pointee_type(),
|
|
&cast<PointerType>(*t2).pointee_type(), equality_ctx);
|
|
case Value::Kind::FunctionType: {
|
|
const auto& fn1 = cast<FunctionType>(*t1);
|
|
const auto& fn2 = cast<FunctionType>(*t2);
|
|
// Verify `self` parameters match
|
|
auto self1 = fn1.method_self();
|
|
auto self2 = fn2.method_self();
|
|
if (self1.has_value() != self2.has_value()) {
|
|
return false;
|
|
}
|
|
if (self1) {
|
|
if (self1->addr_self != self2->addr_self ||
|
|
!TypeEqual(self1->self_type, self2->self_type, equality_ctx)) {
|
|
return false;
|
|
}
|
|
}
|
|
// Verify parameters and return types match
|
|
return TypeEqual(&fn1.parameters(), &fn2.parameters(), equality_ctx) &&
|
|
TypeEqual(&fn1.return_type(), &fn2.return_type(), equality_ctx);
|
|
}
|
|
case Value::Kind::StructType: {
|
|
const auto& struct1 = cast<StructType>(*t1);
|
|
const auto& struct2 = cast<StructType>(*t2);
|
|
if (struct1.fields().size() != struct2.fields().size()) {
|
|
return false;
|
|
}
|
|
for (size_t i = 0; i < struct1.fields().size(); ++i) {
|
|
if (struct1.fields()[i].name != struct2.fields()[i].name ||
|
|
!TypeEqual(struct1.fields()[i].value, struct2.fields()[i].value,
|
|
equality_ctx)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
case Value::Kind::NominalClassType: {
|
|
const auto& class1 = cast<NominalClassType>(*t1);
|
|
const auto& class2 = cast<NominalClassType>(*t2);
|
|
return DeclaresSameEntity(class1.declaration(), class2.declaration()) &&
|
|
BindingMapEqual(class1.bindings().args(), class2.bindings().args(),
|
|
equality_ctx);
|
|
}
|
|
case Value::Kind::InterfaceType: {
|
|
const auto& iface1 = cast<InterfaceType>(*t1);
|
|
const auto& iface2 = cast<InterfaceType>(*t2);
|
|
return DeclaresSameEntity(iface1.declaration(), iface2.declaration()) &&
|
|
BindingMapEqual(iface1.bindings().args(), iface2.bindings().args(),
|
|
equality_ctx);
|
|
}
|
|
case Value::Kind::NamedConstraintType: {
|
|
const auto& constraint1 = cast<NamedConstraintType>(*t1);
|
|
const auto& constraint2 = cast<NamedConstraintType>(*t2);
|
|
return DeclaresSameEntity(constraint1.declaration(),
|
|
constraint2.declaration()) &&
|
|
BindingMapEqual(constraint1.bindings().args(),
|
|
constraint2.bindings().args(), equality_ctx);
|
|
}
|
|
case Value::Kind::AssociatedConstant:
|
|
// Associated constants are sometimes types.
|
|
return ValueEqual(t1, t2, equality_ctx);
|
|
case Value::Kind::ConstraintType: {
|
|
const auto& constraint1 = cast<ConstraintType>(*t1);
|
|
const auto& constraint2 = cast<ConstraintType>(*t2);
|
|
if (constraint1.impls_constraints().size() !=
|
|
constraint2.impls_constraints().size() ||
|
|
constraint1.equality_constraints().size() !=
|
|
constraint2.equality_constraints().size() ||
|
|
constraint1.lookup_contexts().size() !=
|
|
constraint2.lookup_contexts().size()) {
|
|
return false;
|
|
}
|
|
for (size_t i = 0; i < constraint1.impls_constraints().size(); ++i) {
|
|
const auto& impl1 = constraint1.impls_constraints()[i];
|
|
const auto& impl2 = constraint2.impls_constraints()[i];
|
|
if (!TypeEqual(impl1.type, impl2.type, equality_ctx) ||
|
|
!TypeEqual(impl1.interface, impl2.interface, equality_ctx)) {
|
|
return false;
|
|
}
|
|
}
|
|
for (size_t i = 0; i < constraint1.equality_constraints().size(); ++i) {
|
|
const auto& equality1 = constraint1.equality_constraints()[i];
|
|
const auto& equality2 = constraint2.equality_constraints()[i];
|
|
if (equality1.values.size() != equality2.values.size()) {
|
|
return false;
|
|
}
|
|
for (size_t j = 0; j < equality1.values.size(); ++j) {
|
|
if (!ValueEqual(equality1.values[j], equality2.values[j],
|
|
equality_ctx)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
for (size_t i = 0; i < constraint1.lookup_contexts().size(); ++i) {
|
|
const auto& context1 = constraint1.lookup_contexts()[i];
|
|
const auto& context2 = constraint2.lookup_contexts()[i];
|
|
if (!TypeEqual(context1.context, context2.context, equality_ctx)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
case Value::Kind::ChoiceType: {
|
|
const auto& choice1 = cast<ChoiceType>(*t1);
|
|
const auto& choice2 = cast<ChoiceType>(*t2);
|
|
return DeclaresSameEntity(choice1.declaration(), choice2.declaration()) &&
|
|
BindingMapEqual(choice1.type_args(), choice2.type_args(),
|
|
equality_ctx);
|
|
}
|
|
case Value::Kind::TupleType:
|
|
case Value::Kind::TupleValue: {
|
|
const auto& tup1 = cast<TupleValueBase>(*t1);
|
|
const auto& tup2 = cast<TupleValueBase>(*t2);
|
|
if (tup1.elements().size() != tup2.elements().size()) {
|
|
return false;
|
|
}
|
|
for (size_t i = 0; i < tup1.elements().size(); ++i) {
|
|
if (!TypeEqual(tup1.elements()[i], tup2.elements()[i], equality_ctx)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
case Value::Kind::IntType:
|
|
case Value::Kind::BoolType:
|
|
case Value::Kind::TypeType:
|
|
case Value::Kind::StringType:
|
|
return true;
|
|
case Value::Kind::VariableType:
|
|
return &cast<VariableType>(*t1).binding() ==
|
|
&cast<VariableType>(*t2).binding();
|
|
case Value::Kind::StaticArrayType: {
|
|
const auto& array1 = cast<StaticArrayType>(*t1);
|
|
const auto& array2 = cast<StaticArrayType>(*t2);
|
|
return TypeEqual(&array1.element_type(), &array2.element_type(),
|
|
equality_ctx) &&
|
|
array1.size() == array2.size();
|
|
}
|
|
case Value::Kind::IntValue:
|
|
case Value::Kind::BoolValue:
|
|
case Value::Kind::DestructorValue:
|
|
case Value::Kind::FunctionValue:
|
|
case Value::Kind::BoundMethodValue:
|
|
case Value::Kind::StructValue:
|
|
case Value::Kind::NominalClassValue:
|
|
case Value::Kind::AlternativeValue:
|
|
case Value::Kind::AlternativeConstructorValue:
|
|
case Value::Kind::StringValue:
|
|
case Value::Kind::PointerValue:
|
|
case Value::Kind::LocationValue:
|
|
case Value::Kind::ReferenceExpressionValue:
|
|
case Value::Kind::BindingPlaceholderValue:
|
|
case Value::Kind::AddrValue:
|
|
case Value::Kind::UninitializedValue:
|
|
case Value::Kind::ParameterizedEntityName:
|
|
case Value::Kind::MemberName:
|
|
case Value::Kind::TypeOfParameterizedEntityName:
|
|
case Value::Kind::TypeOfMemberName:
|
|
case Value::Kind::MixinPseudoType:
|
|
case Value::Kind::TypeOfMixinPseudoType:
|
|
case Value::Kind::TypeOfNamespaceName:
|
|
CARBON_FATAL("TypeEqual used to compare non-type values\n{0}\n{1}", *t1,
|
|
*t2);
|
|
case Value::Kind::ImplWitness:
|
|
case Value::Kind::BindingWitness:
|
|
case Value::Kind::ConstraintWitness:
|
|
case Value::Kind::ConstraintImplWitness:
|
|
CARBON_FATAL("TypeEqual: unexpected Witness");
|
|
break;
|
|
case Value::Kind::AutoType:
|
|
CARBON_FATAL("TypeEqual: unexpected AutoType");
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Returns true if the two values are known to be equal and are written in the
|
|
// same way at the top level.
|
|
static auto ValueStructurallyEqual(
|
|
Nonnull<const Value*> v1, Nonnull<const Value*> v2,
|
|
std::optional<Nonnull<const EqualityContext*>> equality_ctx) -> bool {
|
|
if (v1 == v2) {
|
|
return true;
|
|
}
|
|
if (v1->kind() != v2->kind()) {
|
|
return false;
|
|
}
|
|
switch (v1->kind()) {
|
|
case Value::Kind::IntValue:
|
|
return cast<IntValue>(*v1).value() == cast<IntValue>(*v2).value();
|
|
case Value::Kind::BoolValue:
|
|
return cast<BoolValue>(*v1).value() == cast<BoolValue>(*v2).value();
|
|
case Value::Kind::FunctionValue: {
|
|
std::optional<Nonnull<const Statement*>> body1 =
|
|
cast<FunctionValue>(*v1).declaration().body();
|
|
std::optional<Nonnull<const Statement*>> body2 =
|
|
cast<FunctionValue>(*v2).declaration().body();
|
|
return body1.has_value() == body2.has_value() &&
|
|
(!body1.has_value() || *body1 == *body2);
|
|
}
|
|
case Value::Kind::DestructorValue:
|
|
return false;
|
|
case Value::Kind::BoundMethodValue: {
|
|
const auto& m1 = cast<BoundMethodValue>(*v1);
|
|
const auto& m2 = cast<BoundMethodValue>(*v2);
|
|
std::optional<Nonnull<const Statement*>> body1 = m1.declaration().body();
|
|
std::optional<Nonnull<const Statement*>> body2 = m2.declaration().body();
|
|
return ValueEqual(m1.receiver(), m2.receiver(), equality_ctx) &&
|
|
body1.has_value() == body2.has_value() &&
|
|
(!body1.has_value() || *body1 == *body2);
|
|
}
|
|
case Value::Kind::TupleType:
|
|
case Value::Kind::TupleValue: {
|
|
const std::vector<Nonnull<const Value*>>& elements1 =
|
|
cast<TupleValueBase>(*v1).elements();
|
|
const std::vector<Nonnull<const Value*>>& elements2 =
|
|
cast<TupleValueBase>(*v2).elements();
|
|
if (elements1.size() != elements2.size()) {
|
|
return false;
|
|
}
|
|
for (size_t i = 0; i < elements1.size(); ++i) {
|
|
if (!ValueEqual(elements1[i], elements2[i], equality_ctx)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
case Value::Kind::StructValue: {
|
|
const auto& struct_v1 = cast<StructValue>(*v1);
|
|
const auto& struct_v2 = cast<StructValue>(*v2);
|
|
CARBON_CHECK(struct_v1.elements().size() == struct_v2.elements().size());
|
|
for (size_t i = 0; i < struct_v1.elements().size(); ++i) {
|
|
CARBON_CHECK(struct_v1.elements()[i].name ==
|
|
struct_v2.elements()[i].name);
|
|
if (!ValueEqual(struct_v1.elements()[i].value,
|
|
struct_v2.elements()[i].value, equality_ctx)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
case Value::Kind::AlternativeValue: {
|
|
const auto& alt1 = cast<AlternativeValue>(*v1);
|
|
const auto& alt2 = cast<AlternativeValue>(*v2);
|
|
if (!TypeEqual(&alt1.choice(), &alt2.choice(), equality_ctx) ||
|
|
&alt1.alternative() != &alt2.alternative()) {
|
|
return false;
|
|
}
|
|
CARBON_CHECK(alt1.argument().has_value() == alt2.argument().has_value());
|
|
return !alt1.argument().has_value() ||
|
|
ValueEqual(*alt1.argument(), *alt2.argument(), equality_ctx);
|
|
}
|
|
case Value::Kind::StringValue:
|
|
return cast<StringValue>(*v1).value() == cast<StringValue>(*v2).value();
|
|
case Value::Kind::ParameterizedEntityName: {
|
|
std::optional<std::string_view> name1 =
|
|
GetName(cast<ParameterizedEntityName>(v1)->declaration());
|
|
std::optional<std::string_view> name2 =
|
|
GetName(cast<ParameterizedEntityName>(v2)->declaration());
|
|
CARBON_CHECK(name1.has_value() && name2.has_value(),
|
|
"parameterized name refers to unnamed declaration");
|
|
return *name1 == *name2;
|
|
}
|
|
case Value::Kind::AssociatedConstant: {
|
|
// The witness value is not part of determining value equality.
|
|
const auto& assoc1 = cast<AssociatedConstant>(*v1);
|
|
const auto& assoc2 = cast<AssociatedConstant>(*v2);
|
|
return DeclaresSameEntity(assoc1.constant(), assoc2.constant()) &&
|
|
TypeEqual(&assoc1.base(), &assoc2.base(), equality_ctx) &&
|
|
TypeEqual(&assoc1.interface(), &assoc2.interface(), equality_ctx);
|
|
}
|
|
case Value::Kind::IntType:
|
|
case Value::Kind::BoolType:
|
|
case Value::Kind::TypeType:
|
|
case Value::Kind::FunctionType:
|
|
case Value::Kind::PointerType:
|
|
case Value::Kind::AutoType:
|
|
case Value::Kind::StructType:
|
|
case Value::Kind::NominalClassType:
|
|
case Value::Kind::MixinPseudoType:
|
|
case Value::Kind::InterfaceType:
|
|
case Value::Kind::NamedConstraintType:
|
|
case Value::Kind::ConstraintType:
|
|
case Value::Kind::ImplWitness:
|
|
case Value::Kind::BindingWitness:
|
|
case Value::Kind::ConstraintWitness:
|
|
case Value::Kind::ConstraintImplWitness:
|
|
case Value::Kind::ChoiceType:
|
|
case Value::Kind::VariableType:
|
|
case Value::Kind::StringType:
|
|
case Value::Kind::TypeOfMixinPseudoType:
|
|
case Value::Kind::TypeOfParameterizedEntityName:
|
|
case Value::Kind::TypeOfMemberName:
|
|
case Value::Kind::TypeOfNamespaceName:
|
|
case Value::Kind::StaticArrayType:
|
|
return TypeEqual(v1, v2, equality_ctx);
|
|
case Value::Kind::NominalClassValue:
|
|
case Value::Kind::BindingPlaceholderValue:
|
|
case Value::Kind::AddrValue:
|
|
case Value::Kind::AlternativeConstructorValue:
|
|
case Value::Kind::PointerValue:
|
|
case Value::Kind::LocationValue:
|
|
case Value::Kind::ReferenceExpressionValue:
|
|
case Value::Kind::UninitializedValue:
|
|
case Value::Kind::MemberName:
|
|
// TODO: support pointer comparisons once we have a clearer distinction
|
|
// between pointers and lvalues.
|
|
CARBON_FATAL("ValueEqual does not support this kind of value: {0}", *v1);
|
|
}
|
|
}
|
|
|
|
// Returns true if the two values are equal and returns false otherwise.
|
|
//
|
|
// This function implements the `==` operator of Carbon.
|
|
auto ValueEqual(Nonnull<const Value*> v1, Nonnull<const Value*> v2,
|
|
std::optional<Nonnull<const EqualityContext*>> equality_ctx)
|
|
-> bool {
|
|
if (v1 == v2) {
|
|
return true;
|
|
}
|
|
|
|
// If we're given an equality context, check to see if it knows these values
|
|
// are equal. Only perform the check if one or the other value is an
|
|
// associated constant; otherwise we should be able to do better by looking
|
|
// at the structures of the values.
|
|
if (equality_ctx) {
|
|
if (IsValueKindDependent(v1)) {
|
|
auto visitor = [&](Nonnull<const Value*> maybe_v2) {
|
|
return !ValueStructurallyEqual(v2, maybe_v2, equality_ctx);
|
|
};
|
|
if (!(*equality_ctx)->VisitEqualValues(v1, visitor)) {
|
|
return true;
|
|
}
|
|
}
|
|
if (IsValueKindDependent(v2)) {
|
|
auto visitor = [&](Nonnull<const Value*> maybe_v1) {
|
|
return !ValueStructurallyEqual(v1, maybe_v1, equality_ctx);
|
|
};
|
|
if (!(*equality_ctx)->VisitEqualValues(v2, visitor)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ValueStructurallyEqual(v1, v2, equality_ctx);
|
|
}
|
|
|
|
auto EqualityConstraint::VisitEqualValues(
|
|
Nonnull<const Value*> value,
|
|
llvm::function_ref<bool(Nonnull<const Value*>)> visitor) const -> bool {
|
|
// See if the given value is part of this constraint.
|
|
auto first_equal = llvm::find_if(values, [value](Nonnull<const Value*> val) {
|
|
return ValueEqual(value, val, std::nullopt);
|
|
});
|
|
if (first_equal == values.end()) {
|
|
return true;
|
|
}
|
|
|
|
// The value is in this group; pass all non-identical values in the group
|
|
// to the visitor. First visit the values we already compared.
|
|
for (const auto* val : llvm::make_range(values.begin(), first_equal)) {
|
|
if (!visitor(val)) {
|
|
return false;
|
|
}
|
|
}
|
|
// Then visit any remaining non-identical values, skipping the one we already
|
|
// found was identical.
|
|
++first_equal;
|
|
for (const auto* val : llvm::make_range(first_equal, values.end())) {
|
|
if (!ValueEqual(value, val, std::nullopt) && !visitor(val)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
auto ConstraintType::VisitEqualValues(
|
|
Nonnull<const Value*> value,
|
|
llvm::function_ref<bool(Nonnull<const Value*>)> visitor) const -> bool {
|
|
for (const auto& eq : equality_constraints()) {
|
|
if (!eq.VisitEqualValues(value, visitor)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
auto FindFunction(std::string_view name,
|
|
llvm::ArrayRef<Nonnull<Declaration*>> members)
|
|
-> std::optional<Nonnull<const FunctionValue*>> {
|
|
for (const auto& member : members) {
|
|
switch (member->kind()) {
|
|
case DeclarationKind::MixDeclaration: {
|
|
const auto& mix_decl = cast<MixDeclaration>(*member);
|
|
Nonnull<const MixinPseudoType*> mixin = &mix_decl.mixin_value();
|
|
const auto res = mixin->FindFunction(name);
|
|
if (res.has_value()) {
|
|
return res;
|
|
}
|
|
break;
|
|
}
|
|
case DeclarationKind::FunctionDeclaration: {
|
|
const auto& fun = cast<FunctionDeclaration>(*member);
|
|
if (fun.name().inner_name() == name) {
|
|
return &cast<FunctionValue>(**fun.constant_value());
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
// TODO: Find out a way to remove code duplication
|
|
auto MixinPseudoType::FindFunction(const std::string_view& name) const
|
|
-> std::optional<Nonnull<const FunctionValue*>> {
|
|
for (const auto& member : declaration().members()) {
|
|
switch (member->kind()) {
|
|
case DeclarationKind::MixDeclaration: {
|
|
const auto& mix_decl = cast<MixDeclaration>(*member);
|
|
Nonnull<const MixinPseudoType*> mixin = &mix_decl.mixin_value();
|
|
const auto res = mixin->FindFunction(name);
|
|
if (res.has_value()) {
|
|
return res;
|
|
}
|
|
break;
|
|
}
|
|
case DeclarationKind::FunctionDeclaration: {
|
|
const auto& fun = cast<FunctionDeclaration>(*member);
|
|
if (fun.name().inner_name() == name) {
|
|
return &cast<FunctionValue>(**fun.constant_value());
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
auto FindFunctionWithParents(std::string_view name,
|
|
const ClassDeclaration& class_decl)
|
|
-> std::optional<Nonnull<const FunctionValue*>> {
|
|
if (auto fun = FindFunction(name, class_decl.members()); fun.has_value()) {
|
|
return fun;
|
|
}
|
|
if (const auto base_type = class_decl.base_type(); base_type.has_value()) {
|
|
return FindFunctionWithParents(name, base_type.value()->declaration());
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
auto FindMember(std::string_view name,
|
|
llvm::ArrayRef<Nonnull<Declaration*>> members)
|
|
-> std::optional<Nonnull<const Declaration*>> {
|
|
for (Nonnull<const Declaration*> member : members) {
|
|
if (std::optional<std::string_view> mem_name = GetName(*member);
|
|
mem_name.has_value()) {
|
|
if (*mem_name == name) {
|
|
return member;
|
|
}
|
|
}
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
void ImplBinding::Print(llvm::raw_ostream& out) const {
|
|
out << "impl binding " << *type_var_ << " as " << **iface_;
|
|
}
|
|
|
|
void ImplBinding::PrintID(llvm::raw_ostream& out) const {
|
|
out << *type_var_ << " as " << **iface_;
|
|
}
|
|
|
|
auto NominalClassType::InheritsClass(Nonnull<const Value*> other) const
|
|
-> bool {
|
|
const auto* other_class = dyn_cast<NominalClassType>(other);
|
|
if (!other_class) {
|
|
return false;
|
|
}
|
|
std::optional<Nonnull<const NominalClassType*>> ancestor_class = this;
|
|
while (ancestor_class) {
|
|
if (TypeEqual(*ancestor_class, other_class, std::nullopt)) {
|
|
return true;
|
|
}
|
|
ancestor_class = (*ancestor_class)->base();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
auto ExpressionCategoryToString(ExpressionCategory cat) -> llvm::StringRef {
|
|
switch (cat) {
|
|
case ExpressionCategory::Value:
|
|
return "value";
|
|
case ExpressionCategory::Reference:
|
|
return "reference";
|
|
case ExpressionCategory::Initializing:
|
|
return "initializing";
|
|
}
|
|
}
|
|
|
|
} // namespace Carbon
|