diff --git a/common/BUILD b/common/BUILD new file mode 100644 index 000000000000..d11aaf2ed34e --- /dev/null +++ b/common/BUILD @@ -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 + +package(default_visibility = ["//visibility:public"]) + +cc_library( + name = "indirect_value", + srcs = ["indirect_value.h"], +) + +cc_test( + name = "indirect_value_test", + srcs = ["indirect_value_test.cpp"], + deps = [ + ":indirect_value", + "@llvm-project//llvm:gtest", + "@llvm-project//llvm:gtest_main", + ], +) diff --git a/common/indirect_value.h b/common/indirect_value.h new file mode 100644 index 000000000000..0ed88702dccb --- /dev/null +++ b/common/indirect_value.h @@ -0,0 +1,108 @@ +// 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 + +#ifndef COMMON_INDIRECT_VALUE_H_ +#define COMMON_INDIRECT_VALUE_H_ + +#include +#include +#include + +namespace Carbon { + +template +class IndirectValue; + +// Creates and returns an IndirectValue that holds the value returned by +// `callable()`. +template +auto CreateIndirectValue(Callable callable) + -> IndirectValue>; + +// An IndirectValue object stores a T value, using a layer of indirection +// that allows us to name the IndirectValue type, and even access the +// underlying T value, in a context where T is not a complete type. This makes +// it useful for things like defining recursive types. T must be an object type. +// +// The underlying value is accessed using the * and -> operators, but +// IndirectValue does not otherwise behave like a pointer: it has no null state, +// and separate IndirectValue objects are never aliases for the same T object. +// Instead, an IndirectValue object behaves as much as possible like a T object: +// the default constructor, copy operations, and move operations all delegate to +// the corresponding operations on T, and a const IndirectValue object provides +// only const access to the underlying T object. The address of the underlying T +// object remains the same throughout the lifetime of the IndirectValue. +// +// IndirectValue is inspired by the indirect_value library proposed in +// http://wg21.link/P1950R1, but makes some different design choices (notably, +// not having an empty state) in order to provide a more value-like API. +template +class IndirectValue { + public: + // TODO(geoffromer): consider using enable_if to disable constructors and + // assignment operators when they wouldn't compile, so that traits like + // std::is_constructible give correct answers. + + // Initializes the underlying T object as if by `T()`. + IndirectValue() : value(std::make_unique()) {} + + // Initializes the underlying T object as if by `T(std::move(value))`. + IndirectValue(T value) : value(std::make_unique(std::move(value))) {} + + // TODO(geoffromer): consider defining implicit conversions from + // U and IndirectValue, when U is implicitly convertible to T. + + IndirectValue(const IndirectValue& other) + : value(std::make_unique(*other)) {} + + IndirectValue(IndirectValue&& other) + : value(std::make_unique(std::move(*other))) {} + + auto operator=(const IndirectValue& other) -> IndirectValue& { + *value = *other.value; + return *this; + } + + auto operator=(IndirectValue&& other) -> IndirectValue& { + *value = std::move(*other.value); + return *this; + } + + auto operator*() -> T& { return *value; } + auto operator*() const -> const T& { return *value; } + + auto operator->() -> T* { return value.get(); } + auto operator->() const -> const T* { return value.get(); } + + // Returns the address of the stored value. + // + // TODO(geoffromer): Consider eliminating this method, which is not + // present in comparable types like indirect_value or optional, + // once our APIs are less pointer-centric. + auto GetPointer() -> T* { return value.get(); } + auto GetPointer() const -> const T* { return value.get(); } + + private: + static_assert(std::is_object_v, "T must be an object type"); + + template + friend auto CreateIndirectValue(Callable callable) + -> IndirectValue>; + + template + IndirectValue(std::unique_ptr value) : value(std::move(value)) {} + + const std::unique_ptr value; +}; + +template +auto CreateIndirectValue(Callable callable) + -> IndirectValue> { + using T = std::decay_t; + return IndirectValue(std::unique_ptr(new T(callable()))); +} + +} // namespace Carbon + +#endif // COMMON_INDIRECT_VALUE_H_ diff --git a/common/indirect_value_test.cpp b/common/indirect_value_test.cpp new file mode 100644 index 000000000000..1c5673e00eb7 --- /dev/null +++ b/common/indirect_value_test.cpp @@ -0,0 +1,125 @@ +// 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/indirect_value.h" + +#include + +#include "gtest/gtest.h" + +namespace Carbon { +namespace { + +TEST(IndirectValueTest, ConstAccess) { + const IndirectValue v = 42; + EXPECT_EQ(*v, 42); + EXPECT_EQ(v.GetPointer(), &*v); +} + +TEST(IndirectValueTest, MutableAccess) { + IndirectValue v = 42; + EXPECT_EQ(*v, 42); + EXPECT_EQ(v.GetPointer(), &*v); + *v = 0; + EXPECT_EQ(*v, 0); +} + +struct NonMovable { + NonMovable(int i) : i(i) {} + NonMovable(NonMovable&&) = delete; + auto operator=(NonMovable&&) -> NonMovable& = delete; + + int i; +}; + +TEST(IndirectValueTest, Create) { + IndirectValue v = + CreateIndirectValue([] { return NonMovable(42); }); + EXPECT_EQ(v->i, 42); +} + +const int& GetIntReference() { + static int i = 42; + return i; +} + +TEST(IndirectValueTest, CreateWithDecay) { + auto v = CreateIndirectValue(GetIntReference); + EXPECT_TRUE((std::is_same_v>)); + EXPECT_EQ(*v, 42); +} + +// Test double which presents a value-like interface, but tracks which special +// member function (if any) caused it to reach its present value. +struct TestValue { + TestValue() : state("default constructed") {} + TestValue(const TestValue& rhs) : state("copy constructed") {} + TestValue(TestValue&& other) : state("move constructed") { + other.state = "move constructed from"; + } + TestValue& operator=(const TestValue&) { + state = "copy assigned"; + return *this; + } + TestValue& operator=(TestValue&& other) { + state = "move assigned"; + other.state = "move assigned from"; + return *this; + } + + std::string state; +}; + +TEST(IndirectValueTest, ConstArrow) { + const IndirectValue v; + EXPECT_EQ(v->state, "default constructed"); +} + +TEST(IndirectValueTest, MutableArrow) { + IndirectValue v; + EXPECT_EQ(v->state, "default constructed"); + v->state = "explicitly set"; + EXPECT_EQ(v->state, "explicitly set"); +} + +TEST(IndirectValueTest, CopyConstruct) { + IndirectValue v1; + auto v2 = v1; + EXPECT_EQ(v1->state, "default constructed"); + EXPECT_EQ(v2->state, "copy constructed"); +} + +TEST(IndirectValueTest, CopyAssign) { + IndirectValue v1; + IndirectValue v2; + v2 = v1; + EXPECT_EQ(v1->state, "default constructed"); + EXPECT_EQ(v2->state, "copy assigned"); +} + +TEST(IndirectValueTest, MoveConstruct) { + IndirectValue v1; + auto v2 = std::move(v1); + EXPECT_EQ(v1->state, "move constructed from"); + EXPECT_EQ(v2->state, "move constructed"); +} + +TEST(IndirectValueTest, MoveAssign) { + IndirectValue v1; + IndirectValue v2; + v2 = std::move(v1); + EXPECT_EQ(v1->state, "move assigned from"); + EXPECT_EQ(v2->state, "move assigned"); +} + +TEST(IndirectValueTest, IncompleteType) { + struct S { + std::optional> v; + }; + + S s = {.v = S{}}; +} + +} // namespace +} // namespace Carbon diff --git a/executable_semantics/ast/BUILD b/executable_semantics/ast/BUILD index 1f7ecb36e7aa..1b755bcd7268 100644 --- a/executable_semantics/ast/BUILD +++ b/executable_semantics/ast/BUILD @@ -25,6 +25,7 @@ cc_library( name = "expression", srcs = ["expression.cpp"], hdrs = ["expression.h"], + deps = ["//common:indirect_value"], ) cc_library( diff --git a/executable_semantics/ast/expression.cpp b/executable_semantics/ast/expression.cpp index f643ee32e412..b35740b94c24 100644 --- a/executable_semantics/ast/expression.cpp +++ b/executable_semantics/ast/expression.cpp @@ -162,7 +162,7 @@ auto Expression::MakeGetField(int line_num, const Expression* exp, std::string field) -> const Expression* { auto* e = new Expression(); e->line_num = line_num; - e->value = FieldAccess({.aggregate = exp, .field = std::move(field)}); + e->value = FieldAccess({.aggregate = *exp, .field = std::move(field)}); return e; } @@ -258,7 +258,7 @@ void PrintExp(const Expression* e) { std::cout << "]"; break; case ExpressionKind::GetField: - PrintExp(e->GetFieldAccess().aggregate); + PrintExp(e->GetFieldAccess().aggregate.GetPointer()); std::cout << "."; std::cout << e->GetFieldAccess().field; break; diff --git a/executable_semantics/ast/expression.h b/executable_semantics/ast/expression.h index 64835d88970a..2ba68a3436af 100644 --- a/executable_semantics/ast/expression.h +++ b/executable_semantics/ast/expression.h @@ -9,6 +9,8 @@ #include #include +#include "common/indirect_value.h" + namespace Carbon { struct Expression; @@ -62,7 +64,7 @@ struct Variable { struct FieldAccess { static constexpr ExpressionKind Kind = ExpressionKind::GetField; - const Expression* aggregate; + IndirectValue aggregate; std::string field; }; diff --git a/executable_semantics/interpreter/interpreter.cpp b/executable_semantics/interpreter/interpreter.cpp index ed089716342d..ed9f298c7a05 100644 --- a/executable_semantics/interpreter/interpreter.cpp +++ b/executable_semantics/interpreter/interpreter.cpp @@ -638,7 +638,8 @@ void StepLvalue() { case ExpressionKind::GetField: { // { {e.f :: C, E, F} :: S, H} // -> { e :: [].f :: C, E, F} :: S, H} - frame->todo.Push(MakeLvalAct(exp->GetFieldAccess().aggregate)); + frame->todo.Push( + MakeLvalAct(exp->GetFieldAccess().aggregate.GetPointer())); act->pos++; break; } @@ -714,7 +715,8 @@ void StepExp() { case ExpressionKind::GetField: { // { { e.f :: C, E, F} :: S, H} // -> { { e :: [].f :: C, E, F} :: S, H} - frame->todo.Push(MakeLvalAct(exp->GetFieldAccess().aggregate)); + frame->todo.Push( + MakeLvalAct(exp->GetFieldAccess().aggregate.GetPointer())); act->pos++; break; } diff --git a/executable_semantics/interpreter/typecheck.cpp b/executable_semantics/interpreter/typecheck.cpp index 57adb7d8331d..60d6dea6ff8d 100644 --- a/executable_semantics/interpreter/typecheck.cpp +++ b/executable_semantics/interpreter/typecheck.cpp @@ -233,8 +233,8 @@ auto TypeCheckExp(const Expression* e, TypeEnv types, Env values, return TCResult(tuple_e, tuple_t, new_types); } case ExpressionKind::GetField: { - auto res = TypeCheckExp(e->GetFieldAccess().aggregate, types, values, - nullptr, TCContext::ValueContext); + auto res = TypeCheckExp(e->GetFieldAccess().aggregate.GetPointer(), types, + values, nullptr, TCContext::ValueContext); auto t = res.type; switch (t->tag) { case ValKind::StructTV: