Implement GnValue FFI and starlark/rust conversions

Bug: 528225104
Change-Id: Ib0c5c1c764ad6bb40f3127ea8daa1d346a6a6964
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/23883
Reviewed-by: Richard Wang <richardwa@google.com>
Reviewed-by: Takuto Ikuta <tikuta@google.com>
Commit-Queue: Matt Stark <msta@google.com>
diff --git a/build/gen.py b/build/gen.py
index e3f2c46..d947852 100755
--- a/build/gen.py
+++ b/build/gen.py
@@ -731,6 +731,8 @@
               'src/gn/escape.cc',
               'src/gn/exec_process.cc',
               'src/gn/ffi/bridge.cc',
+              'src/gn/ffi/scope.cc',
+              'src/gn/ffi/value.cc',
               'src/gn/filesystem_utils.cc',
               'src/gn/file_writer.cc',
               'src/gn/frameworks_utils.cc',
diff --git a/src/gn/ffi/bridge.cc b/src/gn/ffi/bridge.cc
index 1346937..e7eb883 100644
--- a/src/gn/ffi/bridge.cc
+++ b/src/gn/ffi/bridge.cc
@@ -1,23 +1,32 @@
 // This file is generated by src/gn/ffi/update_bridge.sh. Do not edit manually.
 // Source: src/gn/ffi/starlark/crates/ffi/src/bridge.rs
+#include "gn/ffi/scope.h"
 #include "gn/ffi/test_with_scope.h"
+#include "gn/ffi/value.h"
 #include "gn/label.h"
 #include "gn/output_file.h"
 #include "gn/scope.h"
 #include "gn/settings.h"
 #include "gn/source_dir.h"
 #include "gn/test_with_scope.h"
+#include "gn/value.h"
 #include <array>
+#include <cassert>
 #include <cstddef>
 #include <cstdint>
+#include <iterator>
 #include <memory>
 #include <new>
+#include <stdexcept>
 #include <string>
 #include <type_traits>
 #include <utility>
 #if __cplusplus >= 201703L
 #include <string_view>
 #endif
+#if __cplusplus >= 202002L
+#include <ranges>
+#endif
 
 #ifdef __GNUC__
 #pragma GCC diagnostic ignored "-Wmissing-declarations"
@@ -30,12 +39,24 @@
 inline namespace cxxbridge1 {
 // #include "rust/cxx.h"
 
+#ifndef CXXBRIDGE1_PANIC
+#define CXXBRIDGE1_PANIC
+template <typename Exception>
+void panic [[noreturn]](const char *msg);
+#endif // CXXBRIDGE1_PANIC
+
 namespace {
 template <typename T>
 class impl;
 } // namespace
 
 class String;
+class Opaque;
+
+template <typename T>
+::std::size_t size_of();
+template <typename T>
+::std::size_t align_of();
 
 #ifndef CXXBRIDGE1_RUST_STR
 #define CXXBRIDGE1_RUST_STR
@@ -96,6 +117,319 @@
 inline Str cxx_to_rust(const std::string &s) { return Str(s); }
 #endif // CXXBRIDGE1_RUST_STR
 
+#ifndef CXXBRIDGE1_RUST_SLICE
+#define CXXBRIDGE1_RUST_SLICE
+namespace detail {
+template <bool>
+struct copy_assignable_if {};
+
+template <>
+struct copy_assignable_if<false> {
+  copy_assignable_if() noexcept = default;
+  copy_assignable_if(const copy_assignable_if &) noexcept = default;
+  copy_assignable_if &operator=(const copy_assignable_if &) & noexcept = delete;
+  copy_assignable_if &operator=(copy_assignable_if &&) & noexcept = default;
+};
+} // namespace detail
+
+template <typename T>
+class Slice final
+    : private detail::copy_assignable_if<std::is_const<T>::value> {
+public:
+  using value_type = T;
+
+  Slice() noexcept;
+  Slice(T *, std::size_t count) noexcept;
+
+  template <typename C>
+  explicit Slice(C &c) : Slice(c.data(), c.size()) {}
+
+  Slice &operator=(const Slice<T> &) & noexcept = default;
+  Slice &operator=(Slice<T> &&) & noexcept = default;
+
+  T *data() const noexcept;
+  std::size_t size() const noexcept;
+  std::size_t length() const noexcept;
+  bool empty() const noexcept;
+
+  T &operator[](std::size_t n) const noexcept;
+  T &at(std::size_t n) const;
+  T &front() const noexcept;
+  T &back() const noexcept;
+
+  Slice(const Slice<T> &) noexcept = default;
+  ~Slice() noexcept = default;
+
+  class iterator;
+  iterator begin() const noexcept;
+  iterator end() const noexcept;
+
+  void swap(Slice &) noexcept;
+
+private:
+  class uninit;
+  Slice(uninit) noexcept;
+  friend impl<Slice>;
+  friend void sliceInit(void *, const void *, std::size_t) noexcept;
+  friend void *slicePtr(const void *) noexcept;
+  friend std::size_t sliceLen(const void *) noexcept;
+
+  std::array<std::uintptr_t, 2> repr;
+};
+
+#ifdef __cpp_deduction_guides
+template <typename C>
+explicit Slice(C &c)
+    -> Slice<std::remove_reference_t<decltype(*std::declval<C>().data())>>;
+#endif // __cpp_deduction_guides
+
+template <typename T>
+class Slice<T>::iterator final {
+public:
+#if __cplusplus >= 202002L
+  using iterator_category = std::contiguous_iterator_tag;
+#else
+  using iterator_category = std::random_access_iterator_tag;
+#endif
+  using value_type = T;
+  using element_type = T;
+  using difference_type = std::ptrdiff_t;
+  using pointer = typename std::add_pointer<T>::type;
+  using reference = typename std::add_lvalue_reference<T>::type;
+
+  reference operator*() const noexcept;
+  pointer operator->() const noexcept;
+  reference operator[](difference_type) const noexcept;
+
+  iterator &operator++() noexcept;
+  iterator operator++(int) noexcept;
+  iterator &operator--() noexcept;
+  iterator operator--(int) noexcept;
+
+  iterator &operator+=(difference_type) noexcept;
+  iterator &operator-=(difference_type) noexcept;
+  iterator operator+(difference_type) const noexcept;
+  friend inline iterator operator+(difference_type lhs, iterator rhs) noexcept {
+    return rhs + lhs;
+  }
+  iterator operator-(difference_type) const noexcept;
+  difference_type operator-(const iterator &) const noexcept;
+
+  bool operator==(const iterator &) const noexcept;
+  bool operator!=(const iterator &) const noexcept;
+  bool operator<(const iterator &) const noexcept;
+  bool operator<=(const iterator &) const noexcept;
+  bool operator>(const iterator &) const noexcept;
+  bool operator>=(const iterator &) const noexcept;
+
+private:
+  friend class Slice;
+  void *pos;
+  std::size_t stride;
+};
+
+#if __cplusplus >= 202002L
+static_assert(std::ranges::contiguous_range<rust::Slice<const uint8_t>>);
+static_assert(std::contiguous_iterator<rust::Slice<const uint8_t>::iterator>);
+#endif
+
+template <typename T>
+Slice<T>::Slice() noexcept {
+  sliceInit(this, reinterpret_cast<void *>(align_of<T>()), 0);
+}
+
+template <typename T>
+Slice<T>::Slice(T *s, std::size_t count) noexcept {
+  assert(s != nullptr || count == 0);
+  sliceInit(this,
+            s == nullptr && count == 0
+                ? reinterpret_cast<void *>(align_of<T>())
+                : const_cast<typename std::remove_const<T>::type *>(s),
+            count);
+}
+
+template <typename T>
+T *Slice<T>::data() const noexcept {
+  return reinterpret_cast<T *>(slicePtr(this));
+}
+
+template <typename T>
+std::size_t Slice<T>::size() const noexcept {
+  return sliceLen(this);
+}
+
+template <typename T>
+std::size_t Slice<T>::length() const noexcept {
+  return this->size();
+}
+
+template <typename T>
+bool Slice<T>::empty() const noexcept {
+  return this->size() == 0;
+}
+
+template <typename T>
+T &Slice<T>::operator[](std::size_t n) const noexcept {
+  assert(n < this->size());
+  auto ptr = static_cast<char *>(slicePtr(this)) + size_of<T>() * n;
+  return *reinterpret_cast<T *>(ptr);
+}
+
+template <typename T>
+T &Slice<T>::at(std::size_t n) const {
+  if (n >= this->size()) {
+    panic<std::out_of_range>("rust::Slice index out of range");
+  }
+  return (*this)[n];
+}
+
+template <typename T>
+T &Slice<T>::front() const noexcept {
+  assert(!this->empty());
+  return (*this)[0];
+}
+
+template <typename T>
+T &Slice<T>::back() const noexcept {
+  assert(!this->empty());
+  return (*this)[this->size() - 1];
+}
+
+template <typename T>
+typename Slice<T>::iterator::reference
+Slice<T>::iterator::operator*() const noexcept {
+  return *static_cast<T *>(this->pos);
+}
+
+template <typename T>
+typename Slice<T>::iterator::pointer
+Slice<T>::iterator::operator->() const noexcept {
+  return static_cast<T *>(this->pos);
+}
+
+template <typename T>
+typename Slice<T>::iterator::reference Slice<T>::iterator::operator[](
+    typename Slice<T>::iterator::difference_type n) const noexcept {
+  auto ptr = static_cast<char *>(this->pos) + this->stride * n;
+  return *reinterpret_cast<T *>(ptr);
+}
+
+template <typename T>
+typename Slice<T>::iterator &Slice<T>::iterator::operator++() noexcept {
+  this->pos = static_cast<char *>(this->pos) + this->stride;
+  return *this;
+}
+
+template <typename T>
+typename Slice<T>::iterator Slice<T>::iterator::operator++(int) noexcept {
+  auto ret = iterator(*this);
+  this->pos = static_cast<char *>(this->pos) + this->stride;
+  return ret;
+}
+
+template <typename T>
+typename Slice<T>::iterator &Slice<T>::iterator::operator--() noexcept {
+  this->pos = static_cast<char *>(this->pos) - this->stride;
+  return *this;
+}
+
+template <typename T>
+typename Slice<T>::iterator Slice<T>::iterator::operator--(int) noexcept {
+  auto ret = iterator(*this);
+  this->pos = static_cast<char *>(this->pos) - this->stride;
+  return ret;
+}
+
+template <typename T>
+typename Slice<T>::iterator &Slice<T>::iterator::operator+=(
+    typename Slice<T>::iterator::difference_type n) noexcept {
+  this->pos = static_cast<char *>(this->pos) + this->stride * n;
+  return *this;
+}
+
+template <typename T>
+typename Slice<T>::iterator &Slice<T>::iterator::operator-=(
+    typename Slice<T>::iterator::difference_type n) noexcept {
+  this->pos = static_cast<char *>(this->pos) - this->stride * n;
+  return *this;
+}
+
+template <typename T>
+typename Slice<T>::iterator Slice<T>::iterator::operator+(
+    typename Slice<T>::iterator::difference_type n) const noexcept {
+  auto ret = iterator(*this);
+  ret.pos = static_cast<char *>(this->pos) + this->stride * n;
+  return ret;
+}
+
+template <typename T>
+typename Slice<T>::iterator Slice<T>::iterator::operator-(
+    typename Slice<T>::iterator::difference_type n) const noexcept {
+  auto ret = iterator(*this);
+  ret.pos = static_cast<char *>(this->pos) - this->stride * n;
+  return ret;
+}
+
+template <typename T>
+typename Slice<T>::iterator::difference_type
+Slice<T>::iterator::operator-(const iterator &other) const noexcept {
+  auto diff = std::distance(static_cast<char *>(other.pos),
+                            static_cast<char *>(this->pos));
+  return diff / static_cast<typename Slice<T>::iterator::difference_type>(
+                    this->stride);
+}
+
+template <typename T>
+bool Slice<T>::iterator::operator==(const iterator &other) const noexcept {
+  return this->pos == other.pos;
+}
+
+template <typename T>
+bool Slice<T>::iterator::operator!=(const iterator &other) const noexcept {
+  return this->pos != other.pos;
+}
+
+template <typename T>
+bool Slice<T>::iterator::operator<(const iterator &other) const noexcept {
+  return this->pos < other.pos;
+}
+
+template <typename T>
+bool Slice<T>::iterator::operator<=(const iterator &other) const noexcept {
+  return this->pos <= other.pos;
+}
+
+template <typename T>
+bool Slice<T>::iterator::operator>(const iterator &other) const noexcept {
+  return this->pos > other.pos;
+}
+
+template <typename T>
+bool Slice<T>::iterator::operator>=(const iterator &other) const noexcept {
+  return this->pos >= other.pos;
+}
+
+template <typename T>
+typename Slice<T>::iterator Slice<T>::begin() const noexcept {
+  iterator it;
+  it.pos = slicePtr(this);
+  it.stride = size_of<T>();
+  return it;
+}
+
+template <typename T>
+typename Slice<T>::iterator Slice<T>::end() const noexcept {
+  iterator it = this->begin();
+  it.pos = static_cast<char *>(it.pos) + it.stride * this->size();
+  return it;
+}
+
+template <typename T>
+void Slice<T>::swap(Slice &rhs) noexcept {
+  std::swap(*this, rhs);
+}
+#endif // CXXBRIDGE1_RUST_SLICE
+
 #ifndef CXXBRIDGE1_IS_COMPLETE
 #define CXXBRIDGE1_IS_COMPLETE
 namespace detail {
@@ -108,6 +442,62 @@
 } // namespace detail
 #endif // CXXBRIDGE1_IS_COMPLETE
 
+#ifndef CXXBRIDGE1_LAYOUT
+#define CXXBRIDGE1_LAYOUT
+class layout {
+  template <typename T>
+  friend std::size_t size_of();
+  template <typename T>
+  friend std::size_t align_of();
+  template <typename T>
+  static typename std::enable_if<std::is_base_of<Opaque, T>::value,
+                                 std::size_t>::type
+  do_size_of() {
+    return T::layout::size();
+  }
+  template <typename T>
+  static typename std::enable_if<!std::is_base_of<Opaque, T>::value,
+                                 std::size_t>::type
+  do_size_of() {
+    return sizeof(T);
+  }
+  template <typename T>
+  static
+      typename std::enable_if<detail::is_complete<T>::value, std::size_t>::type
+      size_of() {
+    return do_size_of<T>();
+  }
+  template <typename T>
+  static typename std::enable_if<std::is_base_of<Opaque, T>::value,
+                                 std::size_t>::type
+  do_align_of() {
+    return T::layout::align();
+  }
+  template <typename T>
+  static typename std::enable_if<!std::is_base_of<Opaque, T>::value,
+                                 std::size_t>::type
+  do_align_of() {
+    return alignof(T);
+  }
+  template <typename T>
+  static
+      typename std::enable_if<detail::is_complete<T>::value, std::size_t>::type
+      align_of() {
+    return do_align_of<T>();
+  }
+};
+
+template <typename T>
+std::size_t size_of() {
+  return layout::size_of<T>();
+}
+
+template <typename T>
+std::size_t align_of() {
+  return layout::align_of<T>();
+}
+#endif // CXXBRIDGE1_LAYOUT
+
 namespace {
 template <bool> struct deleter_if {
   template <typename T> void operator()(T *) {}
@@ -119,12 +509,65 @@
 } // namespace cxxbridge1
 } // namespace rust
 
+#if __cplusplus >= 201402L
+#define CXX_DEFAULT_VALUE(value) = value
+#else
+#define CXX_DEFAULT_VALUE(value)
+#endif
+
+struct Any;
+struct SliceAny;
+struct KeyValue;
+enum class ValueType : ::std::uint8_t;
 using OutputFile = ::OutputFile;
 using SourceDir = ::SourceDir;
 using Label = ::Label;
 using Settings = ::Settings;
 using Scope = ::Scope;
 using TestWithScope = ::TestWithScope;
+using Value = ::Value;
+using ParseNode = ::ParseNode;
+
+#ifndef CXXBRIDGE1_STRUCT_Any
+#define CXXBRIDGE1_STRUCT_Any
+struct Any final {
+  ::std::uint8_t _private CXX_DEFAULT_VALUE(0);
+
+  using IsRelocatable = ::std::true_type;
+};
+#endif // CXXBRIDGE1_STRUCT_Any
+
+#ifndef CXXBRIDGE1_STRUCT_SliceAny
+#define CXXBRIDGE1_STRUCT_SliceAny
+struct SliceAny final {
+  ::std::size_t len CXX_DEFAULT_VALUE(0);
+  ::Any *ptr CXX_DEFAULT_VALUE(nullptr);
+
+  using IsRelocatable = ::std::true_type;
+};
+#endif // CXXBRIDGE1_STRUCT_SliceAny
+
+#ifndef CXXBRIDGE1_STRUCT_KeyValue
+#define CXXBRIDGE1_STRUCT_KeyValue
+struct KeyValue final {
+  ::rust::Str key;
+  ::Value const &value;
+
+  using IsRelocatable = ::std::true_type;
+};
+#endif // CXXBRIDGE1_STRUCT_KeyValue
+
+#ifndef CXXBRIDGE1_ENUM_ValueType
+#define CXXBRIDGE1_ENUM_ValueType
+enum class ValueType : ::std::uint8_t {
+  None = 0,
+  Boolean = 1,
+  Integer = 2,
+  String = 3,
+  List = 4,
+  Scope = 5,
+};
+#endif // CXXBRIDGE1_ENUM_ValueType
 
 extern "C" {
 void cxxbridge1$196$OutputFile$value(::OutputFile const &self, ::rust::Str *return$) noexcept {
@@ -152,6 +595,16 @@
   new (return$) ::Label const *(&(self.*toolchain_label$)());
 }
 
+void cxxbridge1$196$NewScope(::Scope const &parent_scope, ::rust::Slice<::rust::Str const> keys, ::std::unique_ptr<::Scope> &out_scope, ::SliceAny *return$) noexcept {
+  ::SliceAny (*NewScope$)(::Scope const &, ::rust::Slice<::rust::Str const>, ::std::unique_ptr<::Scope> &) = ::NewScope;
+  new (return$) ::SliceAny(NewScope$(parent_scope, keys, out_scope));
+}
+
+void cxxbridge1$196$GetScopeItems(::Scope const &scope, ::SliceAny *return$) noexcept {
+  ::SliceAny (*GetScopeItems$)(::Scope const &) = ::GetScopeItems;
+  new (return$) ::SliceAny(GetScopeItems$(scope));
+}
+
 ::Settings const *cxxbridge1$196$Scope$settings_cxx(::Scope const &self) noexcept {
   ::Settings const *(::Scope::*settings_cxx$)() const = &::Scope::settings;
   return (self.*settings_cxx$)();
@@ -167,6 +620,95 @@
   return (self.*scope_cxx$)();
 }
 
+::Value *cxxbridge1$196$NewValueForTesting() noexcept {
+  ::std::unique_ptr<::Value> (*NewValueForTesting$)() = ::NewValueForTesting;
+  return NewValueForTesting$().release();
+}
+
+::std::size_t cxxbridge1$196$ValueSize() noexcept {
+  ::std::size_t (*ValueSize$)() = ::ValueSize;
+  return ValueSize$();
+}
+
+::ValueType cxxbridge1$196$Value$kind(::Value const &self) noexcept {
+  Value::Type (::Value::*kind$)() const = &::Value::type;
+  return ::rust::cxx_to_rust((self.*kind$)());
+}
+
+void cxxbridge1$196$Value$boolean_value(::Value const &self, bool const **return$) noexcept {
+  bool const &(::Value::*boolean_value$)() const = &::Value::boolean_value;
+  new (return$) bool const *(&(self.*boolean_value$)());
+}
+
+void cxxbridge1$196$Value$int_value(::Value const &self, ::std::int64_t const **return$) noexcept {
+  ::std::int64_t const &(::Value::*int_value$)() const = &::Value::int_value;
+  new (return$) ::std::int64_t const *(&(self.*int_value$)());
+}
+
+void cxxbridge1$196$Value$string_value(::Value const &self, ::rust::Str *return$) noexcept {
+  const std::string& (::Value::*string_value$)() const = &::Value::string_value;
+  new (return$) ::rust::Str(::rust::cxx_to_rust((self.*string_value$)()));
+}
+
+void cxxbridge1$196$list_value_cxx(::Value const &val, ::SliceAny *return$) noexcept {
+  ::SliceAny (*list_value_cxx$)(::Value const &) = ::GetValueList;
+  new (return$) ::SliceAny(list_value_cxx$(val));
+}
+
+::Scope const *cxxbridge1$196$Value$scope_value(::Value const &self) noexcept {
+  ::Scope const *(::Value::*scope_value$)() const = &::Value::scope_value;
+  return (self.*scope_value$)();
+}
+
+void cxxbridge1$196$SetValueNone(::Value &val, ::ParseNode const *origin) noexcept {
+  void (*SetValueNone$)(::Value &, ::ParseNode const *) = ::SetValueNone;
+  SetValueNone$(val, origin);
+}
+
+void cxxbridge1$196$SetValueBool(::Value &val, ::ParseNode const *origin, bool b) noexcept {
+  void (*SetValueBool$)(::Value &, ::ParseNode const *, bool) = ::SetValueBool;
+  SetValueBool$(val, origin, b);
+}
+
+void cxxbridge1$196$SetValueInt(::Value &val, ::ParseNode const *origin, ::std::int64_t i) noexcept {
+  void (*SetValueInt$)(::Value &, ::ParseNode const *, ::std::int64_t) = ::SetValueInt;
+  SetValueInt$(val, origin, i);
+}
+
+void cxxbridge1$196$SetValueString(::Value &val, ::ParseNode const *origin, ::rust::Str s) noexcept {
+  void (*SetValueString$)(::Value &, ::ParseNode const *, ::rust::Str) = ::SetValueString;
+  SetValueString$(val, origin, s);
+}
+
+::Any *cxxbridge1$196$SetValueList(::Value &val, ::ParseNode const *origin, ::std::size_t size) noexcept {
+  ::Any *(*SetValueList$)(::Value &, ::ParseNode const *, ::std::size_t) = ::SetValueList;
+  return SetValueList$(val, origin, size);
+}
+
+void cxxbridge1$196$SetValueScope(::Value &val, ::ParseNode const *origin, ::Scope *scope) noexcept {
+  void (*SetValueScope$)(::Value &, ::ParseNode const *, ::std::unique_ptr<::Scope>) = ::SetValueScope;
+  SetValueScope$(val, origin, ::std::unique_ptr<::Scope>(scope));
+}
+
+static_assert(::rust::detail::is_complete<::std::remove_extent<::Scope>::type>::value, "definition of `::Scope` is required");
+static_assert(sizeof(::std::unique_ptr<::Scope>) == sizeof(void *), "");
+static_assert(alignof(::std::unique_ptr<::Scope>) == alignof(void *), "");
+void cxxbridge1$unique_ptr$Scope$null(::std::unique_ptr<::Scope> *ptr) noexcept {
+  ::new (ptr) ::std::unique_ptr<::Scope>();
+}
+void cxxbridge1$unique_ptr$Scope$raw(::std::unique_ptr<::Scope> *ptr, ::std::unique_ptr<::Scope>::pointer raw) noexcept {
+  ::new (ptr) ::std::unique_ptr<::Scope>(raw);
+}
+::std::unique_ptr<::Scope>::element_type const *cxxbridge1$unique_ptr$Scope$get(::std::unique_ptr<::Scope> const &ptr) noexcept {
+  return ptr.get();
+}
+::std::unique_ptr<::Scope>::pointer cxxbridge1$unique_ptr$Scope$release(::std::unique_ptr<::Scope> &ptr) noexcept {
+  return ptr.release();
+}
+void cxxbridge1$unique_ptr$Scope$drop(::std::unique_ptr<::Scope> *ptr) noexcept {
+  ::rust::deleter_if<::rust::detail::is_complete<::Scope>::value>{}(ptr);
+}
+
 static_assert(::rust::detail::is_complete<::std::remove_extent<::TestWithScope>::type>::value, "definition of `::TestWithScope` is required");
 static_assert(sizeof(::std::unique_ptr<::TestWithScope>) == sizeof(void *), "");
 static_assert(alignof(::std::unique_ptr<::TestWithScope>) == alignof(void *), "");
@@ -185,4 +727,23 @@
 void cxxbridge1$unique_ptr$TestWithScope$drop(::std::unique_ptr<::TestWithScope> *ptr) noexcept {
   ::rust::deleter_if<::rust::detail::is_complete<::TestWithScope>::value>{}(ptr);
 }
+
+static_assert(::rust::detail::is_complete<::std::remove_extent<::Value>::type>::value, "definition of `::Value` is required");
+static_assert(sizeof(::std::unique_ptr<::Value>) == sizeof(void *), "");
+static_assert(alignof(::std::unique_ptr<::Value>) == alignof(void *), "");
+void cxxbridge1$unique_ptr$Value$null(::std::unique_ptr<::Value> *ptr) noexcept {
+  ::new (ptr) ::std::unique_ptr<::Value>();
+}
+void cxxbridge1$unique_ptr$Value$raw(::std::unique_ptr<::Value> *ptr, ::std::unique_ptr<::Value>::pointer raw) noexcept {
+  ::new (ptr) ::std::unique_ptr<::Value>(raw);
+}
+::std::unique_ptr<::Value>::element_type const *cxxbridge1$unique_ptr$Value$get(::std::unique_ptr<::Value> const &ptr) noexcept {
+  return ptr.get();
+}
+::std::unique_ptr<::Value>::pointer cxxbridge1$unique_ptr$Value$release(::std::unique_ptr<::Value> &ptr) noexcept {
+  return ptr.release();
+}
+void cxxbridge1$unique_ptr$Value$drop(::std::unique_ptr<::Value> *ptr) noexcept {
+  ::rust::deleter_if<::rust::detail::is_complete<::Value>::value>{}(ptr);
+}
 } // extern "C"
diff --git a/src/gn/ffi/bridge.h b/src/gn/ffi/bridge.h
index 39ec711..7ec8e6e 100644
--- a/src/gn/ffi/bridge.h
+++ b/src/gn/ffi/bridge.h
@@ -1,31 +1,54 @@
 // This file is generated by src/gn/ffi/update_bridge.sh. Do not edit manually.
 // Source: src/gn/ffi/starlark/crates/ffi/src/bridge.rs
 #pragma once
+#include "gn/ffi/scope.h"
 #include "gn/ffi/test_with_scope.h"
+#include "gn/ffi/value.h"
 #include "gn/label.h"
 #include "gn/output_file.h"
 #include "gn/scope.h"
 #include "gn/settings.h"
 #include "gn/source_dir.h"
 #include "gn/test_with_scope.h"
+#include "gn/value.h"
 #include <array>
+#include <cassert>
+#include <cstddef>
 #include <cstdint>
+#include <iterator>
 #include <memory>
+#include <stdexcept>
 #include <string>
+#include <type_traits>
 #if __cplusplus >= 201703L
 #include <string_view>
 #endif
+#if __cplusplus >= 202002L
+#include <ranges>
+#endif
 
 namespace rust {
 inline namespace cxxbridge1 {
 // #include "rust/cxx.h"
 
+#ifndef CXXBRIDGE1_PANIC
+#define CXXBRIDGE1_PANIC
+template <typename Exception>
+void panic [[noreturn]](const char *msg);
+#endif // CXXBRIDGE1_PANIC
+
 namespace {
 template <typename T>
 class impl;
 } // namespace
 
 class String;
+class Opaque;
+
+template <typename T>
+::std::size_t size_of();
+template <typename T>
+::std::size_t align_of();
 
 #ifndef CXXBRIDGE1_RUST_STR
 #define CXXBRIDGE1_RUST_STR
@@ -85,12 +108,446 @@
 
 inline Str cxx_to_rust(const std::string &s) { return Str(s); }
 #endif // CXXBRIDGE1_RUST_STR
+
+#ifndef CXXBRIDGE1_RUST_SLICE
+#define CXXBRIDGE1_RUST_SLICE
+namespace detail {
+template <bool>
+struct copy_assignable_if {};
+
+template <>
+struct copy_assignable_if<false> {
+  copy_assignable_if() noexcept = default;
+  copy_assignable_if(const copy_assignable_if &) noexcept = default;
+  copy_assignable_if &operator=(const copy_assignable_if &) & noexcept = delete;
+  copy_assignable_if &operator=(copy_assignable_if &&) & noexcept = default;
+};
+} // namespace detail
+
+template <typename T>
+class Slice final
+    : private detail::copy_assignable_if<std::is_const<T>::value> {
+public:
+  using value_type = T;
+
+  Slice() noexcept;
+  Slice(T *, std::size_t count) noexcept;
+
+  template <typename C>
+  explicit Slice(C &c) : Slice(c.data(), c.size()) {}
+
+  Slice &operator=(const Slice<T> &) & noexcept = default;
+  Slice &operator=(Slice<T> &&) & noexcept = default;
+
+  T *data() const noexcept;
+  std::size_t size() const noexcept;
+  std::size_t length() const noexcept;
+  bool empty() const noexcept;
+
+  T &operator[](std::size_t n) const noexcept;
+  T &at(std::size_t n) const;
+  T &front() const noexcept;
+  T &back() const noexcept;
+
+  Slice(const Slice<T> &) noexcept = default;
+  ~Slice() noexcept = default;
+
+  class iterator;
+  iterator begin() const noexcept;
+  iterator end() const noexcept;
+
+  void swap(Slice &) noexcept;
+
+private:
+  class uninit;
+  Slice(uninit) noexcept;
+  friend impl<Slice>;
+  friend void sliceInit(void *, const void *, std::size_t) noexcept;
+  friend void *slicePtr(const void *) noexcept;
+  friend std::size_t sliceLen(const void *) noexcept;
+
+  std::array<std::uintptr_t, 2> repr;
+};
+
+#ifdef __cpp_deduction_guides
+template <typename C>
+explicit Slice(C &c)
+    -> Slice<std::remove_reference_t<decltype(*std::declval<C>().data())>>;
+#endif // __cpp_deduction_guides
+
+template <typename T>
+class Slice<T>::iterator final {
+public:
+#if __cplusplus >= 202002L
+  using iterator_category = std::contiguous_iterator_tag;
+#else
+  using iterator_category = std::random_access_iterator_tag;
+#endif
+  using value_type = T;
+  using element_type = T;
+  using difference_type = std::ptrdiff_t;
+  using pointer = typename std::add_pointer<T>::type;
+  using reference = typename std::add_lvalue_reference<T>::type;
+
+  reference operator*() const noexcept;
+  pointer operator->() const noexcept;
+  reference operator[](difference_type) const noexcept;
+
+  iterator &operator++() noexcept;
+  iterator operator++(int) noexcept;
+  iterator &operator--() noexcept;
+  iterator operator--(int) noexcept;
+
+  iterator &operator+=(difference_type) noexcept;
+  iterator &operator-=(difference_type) noexcept;
+  iterator operator+(difference_type) const noexcept;
+  friend inline iterator operator+(difference_type lhs, iterator rhs) noexcept {
+    return rhs + lhs;
+  }
+  iterator operator-(difference_type) const noexcept;
+  difference_type operator-(const iterator &) const noexcept;
+
+  bool operator==(const iterator &) const noexcept;
+  bool operator!=(const iterator &) const noexcept;
+  bool operator<(const iterator &) const noexcept;
+  bool operator<=(const iterator &) const noexcept;
+  bool operator>(const iterator &) const noexcept;
+  bool operator>=(const iterator &) const noexcept;
+
+private:
+  friend class Slice;
+  void *pos;
+  std::size_t stride;
+};
+
+#if __cplusplus >= 202002L
+static_assert(std::ranges::contiguous_range<rust::Slice<const uint8_t>>);
+static_assert(std::contiguous_iterator<rust::Slice<const uint8_t>::iterator>);
+#endif
+
+template <typename T>
+Slice<T>::Slice() noexcept {
+  sliceInit(this, reinterpret_cast<void *>(align_of<T>()), 0);
+}
+
+template <typename T>
+Slice<T>::Slice(T *s, std::size_t count) noexcept {
+  assert(s != nullptr || count == 0);
+  sliceInit(this,
+            s == nullptr && count == 0
+                ? reinterpret_cast<void *>(align_of<T>())
+                : const_cast<typename std::remove_const<T>::type *>(s),
+            count);
+}
+
+template <typename T>
+T *Slice<T>::data() const noexcept {
+  return reinterpret_cast<T *>(slicePtr(this));
+}
+
+template <typename T>
+std::size_t Slice<T>::size() const noexcept {
+  return sliceLen(this);
+}
+
+template <typename T>
+std::size_t Slice<T>::length() const noexcept {
+  return this->size();
+}
+
+template <typename T>
+bool Slice<T>::empty() const noexcept {
+  return this->size() == 0;
+}
+
+template <typename T>
+T &Slice<T>::operator[](std::size_t n) const noexcept {
+  assert(n < this->size());
+  auto ptr = static_cast<char *>(slicePtr(this)) + size_of<T>() * n;
+  return *reinterpret_cast<T *>(ptr);
+}
+
+template <typename T>
+T &Slice<T>::at(std::size_t n) const {
+  if (n >= this->size()) {
+    panic<std::out_of_range>("rust::Slice index out of range");
+  }
+  return (*this)[n];
+}
+
+template <typename T>
+T &Slice<T>::front() const noexcept {
+  assert(!this->empty());
+  return (*this)[0];
+}
+
+template <typename T>
+T &Slice<T>::back() const noexcept {
+  assert(!this->empty());
+  return (*this)[this->size() - 1];
+}
+
+template <typename T>
+typename Slice<T>::iterator::reference
+Slice<T>::iterator::operator*() const noexcept {
+  return *static_cast<T *>(this->pos);
+}
+
+template <typename T>
+typename Slice<T>::iterator::pointer
+Slice<T>::iterator::operator->() const noexcept {
+  return static_cast<T *>(this->pos);
+}
+
+template <typename T>
+typename Slice<T>::iterator::reference Slice<T>::iterator::operator[](
+    typename Slice<T>::iterator::difference_type n) const noexcept {
+  auto ptr = static_cast<char *>(this->pos) + this->stride * n;
+  return *reinterpret_cast<T *>(ptr);
+}
+
+template <typename T>
+typename Slice<T>::iterator &Slice<T>::iterator::operator++() noexcept {
+  this->pos = static_cast<char *>(this->pos) + this->stride;
+  return *this;
+}
+
+template <typename T>
+typename Slice<T>::iterator Slice<T>::iterator::operator++(int) noexcept {
+  auto ret = iterator(*this);
+  this->pos = static_cast<char *>(this->pos) + this->stride;
+  return ret;
+}
+
+template <typename T>
+typename Slice<T>::iterator &Slice<T>::iterator::operator--() noexcept {
+  this->pos = static_cast<char *>(this->pos) - this->stride;
+  return *this;
+}
+
+template <typename T>
+typename Slice<T>::iterator Slice<T>::iterator::operator--(int) noexcept {
+  auto ret = iterator(*this);
+  this->pos = static_cast<char *>(this->pos) - this->stride;
+  return ret;
+}
+
+template <typename T>
+typename Slice<T>::iterator &Slice<T>::iterator::operator+=(
+    typename Slice<T>::iterator::difference_type n) noexcept {
+  this->pos = static_cast<char *>(this->pos) + this->stride * n;
+  return *this;
+}
+
+template <typename T>
+typename Slice<T>::iterator &Slice<T>::iterator::operator-=(
+    typename Slice<T>::iterator::difference_type n) noexcept {
+  this->pos = static_cast<char *>(this->pos) - this->stride * n;
+  return *this;
+}
+
+template <typename T>
+typename Slice<T>::iterator Slice<T>::iterator::operator+(
+    typename Slice<T>::iterator::difference_type n) const noexcept {
+  auto ret = iterator(*this);
+  ret.pos = static_cast<char *>(this->pos) + this->stride * n;
+  return ret;
+}
+
+template <typename T>
+typename Slice<T>::iterator Slice<T>::iterator::operator-(
+    typename Slice<T>::iterator::difference_type n) const noexcept {
+  auto ret = iterator(*this);
+  ret.pos = static_cast<char *>(this->pos) - this->stride * n;
+  return ret;
+}
+
+template <typename T>
+typename Slice<T>::iterator::difference_type
+Slice<T>::iterator::operator-(const iterator &other) const noexcept {
+  auto diff = std::distance(static_cast<char *>(other.pos),
+                            static_cast<char *>(this->pos));
+  return diff / static_cast<typename Slice<T>::iterator::difference_type>(
+                    this->stride);
+}
+
+template <typename T>
+bool Slice<T>::iterator::operator==(const iterator &other) const noexcept {
+  return this->pos == other.pos;
+}
+
+template <typename T>
+bool Slice<T>::iterator::operator!=(const iterator &other) const noexcept {
+  return this->pos != other.pos;
+}
+
+template <typename T>
+bool Slice<T>::iterator::operator<(const iterator &other) const noexcept {
+  return this->pos < other.pos;
+}
+
+template <typename T>
+bool Slice<T>::iterator::operator<=(const iterator &other) const noexcept {
+  return this->pos <= other.pos;
+}
+
+template <typename T>
+bool Slice<T>::iterator::operator>(const iterator &other) const noexcept {
+  return this->pos > other.pos;
+}
+
+template <typename T>
+bool Slice<T>::iterator::operator>=(const iterator &other) const noexcept {
+  return this->pos >= other.pos;
+}
+
+template <typename T>
+typename Slice<T>::iterator Slice<T>::begin() const noexcept {
+  iterator it;
+  it.pos = slicePtr(this);
+  it.stride = size_of<T>();
+  return it;
+}
+
+template <typename T>
+typename Slice<T>::iterator Slice<T>::end() const noexcept {
+  iterator it = this->begin();
+  it.pos = static_cast<char *>(it.pos) + it.stride * this->size();
+  return it;
+}
+
+template <typename T>
+void Slice<T>::swap(Slice &rhs) noexcept {
+  std::swap(*this, rhs);
+}
+#endif // CXXBRIDGE1_RUST_SLICE
+
+#ifndef CXXBRIDGE1_IS_COMPLETE
+#define CXXBRIDGE1_IS_COMPLETE
+namespace detail {
+namespace {
+template <typename T, typename = std::size_t>
+struct is_complete : std::false_type {};
+template <typename T>
+struct is_complete<T, decltype(sizeof(T))> : std::true_type {};
+} // namespace
+} // namespace detail
+#endif // CXXBRIDGE1_IS_COMPLETE
+
+#ifndef CXXBRIDGE1_LAYOUT
+#define CXXBRIDGE1_LAYOUT
+class layout {
+  template <typename T>
+  friend std::size_t size_of();
+  template <typename T>
+  friend std::size_t align_of();
+  template <typename T>
+  static typename std::enable_if<std::is_base_of<Opaque, T>::value,
+                                 std::size_t>::type
+  do_size_of() {
+    return T::layout::size();
+  }
+  template <typename T>
+  static typename std::enable_if<!std::is_base_of<Opaque, T>::value,
+                                 std::size_t>::type
+  do_size_of() {
+    return sizeof(T);
+  }
+  template <typename T>
+  static
+      typename std::enable_if<detail::is_complete<T>::value, std::size_t>::type
+      size_of() {
+    return do_size_of<T>();
+  }
+  template <typename T>
+  static typename std::enable_if<std::is_base_of<Opaque, T>::value,
+                                 std::size_t>::type
+  do_align_of() {
+    return T::layout::align();
+  }
+  template <typename T>
+  static typename std::enable_if<!std::is_base_of<Opaque, T>::value,
+                                 std::size_t>::type
+  do_align_of() {
+    return alignof(T);
+  }
+  template <typename T>
+  static
+      typename std::enable_if<detail::is_complete<T>::value, std::size_t>::type
+      align_of() {
+    return do_align_of<T>();
+  }
+};
+
+template <typename T>
+std::size_t size_of() {
+  return layout::size_of<T>();
+}
+
+template <typename T>
+std::size_t align_of() {
+  return layout::align_of<T>();
+}
+#endif // CXXBRIDGE1_LAYOUT
 } // namespace cxxbridge1
 } // namespace rust
 
+#if __cplusplus >= 201402L
+#define CXX_DEFAULT_VALUE(value) = value
+#else
+#define CXX_DEFAULT_VALUE(value)
+#endif
+
+struct Any;
+struct SliceAny;
+struct KeyValue;
+enum class ValueType : ::std::uint8_t;
 using OutputFile = ::OutputFile;
 using SourceDir = ::SourceDir;
 using Label = ::Label;
 using Settings = ::Settings;
 using Scope = ::Scope;
 using TestWithScope = ::TestWithScope;
+using Value = ::Value;
+using ParseNode = ::ParseNode;
+
+#ifndef CXXBRIDGE1_STRUCT_Any
+#define CXXBRIDGE1_STRUCT_Any
+struct Any final {
+  ::std::uint8_t _private CXX_DEFAULT_VALUE(0);
+
+  using IsRelocatable = ::std::true_type;
+};
+#endif // CXXBRIDGE1_STRUCT_Any
+
+#ifndef CXXBRIDGE1_STRUCT_SliceAny
+#define CXXBRIDGE1_STRUCT_SliceAny
+struct SliceAny final {
+  ::std::size_t len CXX_DEFAULT_VALUE(0);
+  ::Any *ptr CXX_DEFAULT_VALUE(nullptr);
+
+  using IsRelocatable = ::std::true_type;
+};
+#endif // CXXBRIDGE1_STRUCT_SliceAny
+
+#ifndef CXXBRIDGE1_STRUCT_KeyValue
+#define CXXBRIDGE1_STRUCT_KeyValue
+struct KeyValue final {
+  ::rust::Str key;
+  ::Value const &value;
+
+  using IsRelocatable = ::std::true_type;
+};
+#endif // CXXBRIDGE1_STRUCT_KeyValue
+
+#ifndef CXXBRIDGE1_ENUM_ValueType
+#define CXXBRIDGE1_ENUM_ValueType
+enum class ValueType : ::std::uint8_t {
+  None = 0,
+  Boolean = 1,
+  Integer = 2,
+  String = 3,
+  List = 4,
+  Scope = 5,
+};
+#endif // CXXBRIDGE1_ENUM_ValueType
diff --git a/src/gn/ffi/scope.cc b/src/gn/ffi/scope.cc
new file mode 100644
index 0000000..f3a658e
--- /dev/null
+++ b/src/gn/ffi/scope.cc
@@ -0,0 +1,67 @@
+// Copyright 2026 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/ffi/scope.h"
+#include "gn/ffi/bridge.h"
+#include "gn/ffi/slice.h"
+#include "gn/scope.h"
+#include "gn/value.h"
+
+SliceAny NewScope(const Scope& parent_scope,
+                  rust::Slice<const rust::Str> keys,
+                  std::unique_ptr<Scope>& out_scope) {
+  auto new_scope = std::make_unique<Scope>(&parent_scope);
+  new_scope->set_source_dir(parent_scope.GetSourceDir());
+  // We detach because GN always detaches when result_mode of the parse tree is
+  // RETURNS_SCOPE (which is what starlark creates new scopes for).
+  new_scope->DetachFromContaining();
+
+  std::vector<Value*> placeholders;
+  placeholders.reserve(keys.size());
+  for (const auto& key : keys) {
+    std::string_view key_sv(key.data(), key.size());
+    Value* val = new_scope->SetValue(key_sv, Value(), nullptr);
+    placeholders.push_back(val);
+  }
+
+  out_scope = std::move(new_scope);
+  return IntoSlice(std::move(placeholders));
+}
+
+// Unlike regular GN scoping rules, this does not extract from variables defined
+// in outer scopes. This is because starlark treats scopes as equivalent to
+// "struct" objects, and as the **kwargs to pass to functions. Thus, accessing
+// values from outer scopes would be very wierd.
+// Consider the following example:
+//
+// # //:example.scl
+// def my_macro(srcs, my_struct):
+//    my_struct.bar
+//
+// # BUILD.gn
+// load("//:example.scl", "my_macro")
+//
+// foo = 1
+// my_macro() {
+//   srcs = ...
+//   my_struct = {
+//     bar = 2
+//   }
+// }
+//
+// In this example, if we included parent scopes as well:
+// * my_macro would complain that it got an unexpected parameter "foo"
+// * my_struct.srcs would also be accessible.
+SliceAny GetScopeItems(const Scope& scope) {
+  Scope::KeyValueMap scope_values;
+  scope.GetCurrentScopeValues(&scope_values);
+
+  std::vector<KeyValue> vec;
+  vec.reserve(scope_values.size());
+  for (const auto& pair : scope_values) {
+    vec.push_back(
+        KeyValue{rust::Str(pair.first.data(), pair.first.size()), pair.second});
+  }
+  return IntoSlice(std::move(vec));
+}
diff --git a/src/gn/ffi/scope.h b/src/gn/ffi/scope.h
new file mode 100644
index 0000000..dc3e309
--- /dev/null
+++ b/src/gn/ffi/scope.h
@@ -0,0 +1,29 @@
+// Copyright 2026 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_FFI_SCOPE_H_
+#define TOOLS_GN_FFI_SCOPE_H_
+
+#include <memory>
+
+#include "cxx.h"
+
+class Scope;
+struct SliceAny;
+
+// Constructs a new child Scope, populates placeholder Values for the given
+// keys, and returns a "std::vector<Value&>" where vec[i] is the value for
+// keys[i].
+//
+// Safety: Rust is required to convert this to an OwnedSlice<&Value>.
+SliceAny NewScope(const Scope& parent_scope,
+                  rust::Slice<const rust::Str> keys,
+                  std::unique_ptr<Scope>& out_scope);
+
+// Returns a "std::vector<KeyValue>"-like object.
+//
+// Safety: Rust is required to convert this to an OwnedSlice<KeyValue>.
+SliceAny GetScopeItems(const Scope& scope);
+
+#endif  // TOOLS_GN_FFI_SCOPE_H_
diff --git a/src/gn/ffi/slice.h b/src/gn/ffi/slice.h
new file mode 100644
index 0000000..8729eba
--- /dev/null
+++ b/src/gn/ffi/slice.h
@@ -0,0 +1,54 @@
+// Copyright 2026 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_FFI_SLICE_H_
+#define TOOLS_GN_FFI_SLICE_H_
+
+#include <array>
+#include <cstdint>
+#include <type_traits>
+#include <vector>
+
+#include "gn/ffi/bridge.h"
+
+// Consumes a std::vector<T> to return a Slice to rust.
+// This is akin to unique_pointer.release(), where we give up ownership of the
+// slice and pass it to rust.
+//
+// Safety: Rust is *required* to cast this to an OwnedSlice<T>.
+// This will guaruntee that the vector is destroyed.
+template <typename T>
+inline SliceAny IntoSlice(std::vector<T> vec) {
+  // Rust knows how to free the slice itself (just call free on the pointer),
+  // but does not know how to call the destructor on individual elements.
+  static_assert(std::is_trivially_destructible_v<T>,
+                "T must be trivially destructible to avoid leaks");
+  if (vec.empty()) {
+    return SliceAny{0, nullptr};
+  }
+  SliceAny slice{vec.size(), reinterpret_cast<Any*>(vec.data())};
+
+  // Construct on stack buffer to prevent C++ compiler from running destructor
+  // on vec
+  std::array<uint8_t, sizeof(std::vector<T>)> buf;
+  new (&buf) std::vector<T>(std::move(vec));
+
+  return slice;
+}
+
+// Returns a view of a std::vector, which may or may not be const.
+//
+// Safety: This uses const_cast to cast away constness because SliceAny is a
+// unified FFI representation that uses a mutable Any* pointer (which is
+// required to support both mutable and immutable slices in Rust).
+//
+// If the API intends to return immutable objects, the rust caller is
+// responsible for wrapping the API in a function that returns Immutable<T>.
+template <typename T>
+inline SliceAny AsSlice(const std::vector<T>& vec) {
+  return SliceAny{vec.size(),
+                  reinterpret_cast<Any*>(const_cast<T*>(vec.data()))};
+}
+
+#endif  // TOOLS_GN_FFI_SLICE_H_
diff --git a/src/gn/ffi/value.cc b/src/gn/ffi/value.cc
new file mode 100644
index 0000000..dce39d8
--- /dev/null
+++ b/src/gn/ffi/value.cc
@@ -0,0 +1,57 @@
+//  Copyright 2026 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/ffi/value.h"
+
+#include <new>
+#include <string>
+
+#include "gn/ffi/bridge.h"
+#include "gn/ffi/slice.h"
+
+namespace rust {
+ValueType cxx_to_rust(Value::Type t) {
+  return static_cast<ValueType>(t);
+}
+}  // namespace rust
+
+size_t ValueSize() {
+  return sizeof(Value);
+}
+
+void SetValueNone(Value& self, const ParseNode* origin) {
+  new (&self) Value(origin, Value::NONE);
+}
+
+void SetValueBool(Value& self, const ParseNode* origin, bool b) {
+  new (&self) Value(origin, b);
+}
+
+void SetValueInt(Value& self, const ParseNode* origin, int64_t i) {
+  new (&self) Value(origin, i);
+}
+
+void SetValueString(Value& self, const ParseNode* origin, rust::Str s) {
+  new (&self) Value(origin, std::string(s.data(), s.size()));
+}
+
+Any* SetValueList(Value& self, const ParseNode* origin, size_t size) {
+  new (&self) Value(origin, Value::LIST);
+  self.list_value().resize(size);
+  return reinterpret_cast<Any*>(self.list_value().data());
+}
+
+void SetValueScope(Value& self,
+                   const ParseNode* origin,
+                   std::unique_ptr<Scope> scope) {
+  new (&self) Value(origin, std::move(scope));
+}
+
+SliceAny GetValueList(const Value& self) {
+  return AsSlice(self.list_value());
+}
+
+std::unique_ptr<Value> NewValueForTesting() {
+  return std::make_unique<Value>();
+}
diff --git a/src/gn/ffi/value.h b/src/gn/ffi/value.h
new file mode 100644
index 0000000..5fe8106
--- /dev/null
+++ b/src/gn/ffi/value.h
@@ -0,0 +1,51 @@
+// Copyright 2026 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_FFI_VALUE_H_
+#define TOOLS_GN_FFI_VALUE_H_
+
+#include <memory>
+
+#include "cxx.h"
+#include "gn/value.h"
+
+class Scope;
+class ParseNode;
+struct SliceAny;
+
+enum class ValueType : uint8_t;
+
+// Teach rust how to convert Value::Type to an enum that rust is aware of.
+namespace rust {
+ValueType cxx_to_rust(Value::Type t);
+}
+
+size_t ValueSize();
+// SetValue* is called with potentially uninitialized Value objects.
+// These functions roughly correspond to calling the corresponding constructor
+// with in-place construction.
+void SetValueNone(Value& self, const ParseNode* origin);
+void SetValueBool(Value& self, const ParseNode* origin, bool b);
+void SetValueInt(Value& self, const ParseNode* origin, int64_t i);
+void SetValueString(Value& self, const ParseNode* origin, rust::Str s);
+struct Any;
+// Sets the value to a list of `size` elements. Returns a pointer to the start
+// of the vector.
+//
+// Safety: Rust is required to convert this to a Slice<Value>(pointer, size)
+Any* SetValueList(Value& self, const ParseNode* origin, size_t size);
+void SetValueScope(Value& self,
+                   const ParseNode* origin,
+                   std::unique_ptr<Scope> scope);
+// Returns a "std::vector<Value>".
+//
+// Safety: Rust is required to convert this to a Slice<Value>.
+SliceAny GetValueList(const Value& self);
+
+// GN values are never created in starlark in production code.
+// If a value may ever be returned, it will be passed as a mutable output
+// parameter.
+std::unique_ptr<Value> NewValueForTesting();
+
+#endif  // TOOLS_GN_FFI_VALUE_H_
diff --git a/src/gn/starlark/crates/build_helper.rs b/src/gn/starlark/crates/build_helper.rs
index 7a943eb..30886ac 100644
--- a/src/gn/starlark/crates/build_helper.rs
+++ b/src/gn/starlark/crates/build_helper.rs
@@ -3,11 +3,11 @@
 // found in the LICENSE file.
 
 fn require_lib(out_dir: &std::path::Path, name: &str) {
-    println!("cargo:rustc-link-lib=static={}", name);
+    println!("cargo:rustc-link-lib=static={name}");
     let lib = if cfg!(target_os = "windows") {
-        format!("{}.lib", name)
+        format!("{name}.lib")
     } else {
-        format!("lib{}.a", name)
+        format!("lib{name}.a")
     };
     println!("cargo:rerun-if-changed={}", out_dir.join(lib).display());
 }
diff --git a/src/gn/starlark/crates/ffi/src/bridge.rs b/src/gn/starlark/crates/ffi/src/bridge.rs
index 8898d2a..96f55d4 100644
--- a/src/gn/starlark/crates/ffi/src/bridge.rs
+++ b/src/gn/starlark/crates/ffi/src/bridge.rs
@@ -16,16 +16,44 @@
 // CxxBridge requires a module, but we don't want one. So we make a private one
 // and re-export all fields.
 mod dummy {
+    struct Any {
+        _private: u8,
+    }
+
+    // A &[T] compatible with both opaque and non-opaque types.
+    #[derive(Clone, Copy)]
+    struct SliceAny {
+        len: usize,
+        ptr: *mut Any,
+    }
+
+    struct KeyValue<'a> {
+        key: &'a str,
+        value: &'a Value,
+    }
+
+    #[derive(Clone, Copy)]
+    enum ValueType {
+        None = 0,
+        Boolean = 1,
+        Integer = 2,
+        String = 3,
+        List = 4,
+        Scope = 5,
+    }
     unsafe extern "C++" {
         // include! simply tells cxxbridge to put the #include in the generated C++
         // source code. It does not do anything on the rust side.
+        include!("gn/ffi/scope.h");
         include!("gn/ffi/test_with_scope.h");
+        include!("gn/ffi/value.h");
         include!("gn/label.h");
         include!("gn/output_file.h");
         include!("gn/scope.h");
         include!("gn/settings.h");
         include!("gn/source_dir.h");
         include!("gn/test_with_scope.h");
+        include!("gn/value.h");
 
         type OutputFile;
         #[cxx_return_type = "std::string_view"]
@@ -44,13 +72,72 @@
         pub(in crate::settings) fn toolchain_label(self: &Settings) -> &Label;
 
         type Scope;
+        // Constructs a new child Scope, populates placeholder Values for the given
+        // keys, and returns an owned slice of references to the placeholders.
+        // For example, NewScope(&scope, ["foo", "bar"]) would return
+        // [scope["foo"], scope["bar"]].
+        // The caller is then responsible for filling in the values as needed.
+        pub(in crate::scope) fn NewScope(
+            parent_scope: &Scope,
+            keys: &[&str],
+            out_scope: &mut UniquePtr<Scope>,
+        ) -> SliceAny;
+        // Returns an OwnedSlice<KeyValue> corresponding to references to each element.
+        pub(in crate::scope) fn GetScopeItems(scope: &Scope) -> SliceAny;
         #[rust_name = "settings_cxx"]
-        pub(crate) fn settings(self: &Scope) -> *const Settings;
+        pub(in crate::scope) fn settings(self: &Scope) -> *const Settings;
 
         type TestWithScope;
         pub(in crate::test_with_scope) fn NewTestWithScope() -> UniquePtr<TestWithScope>;
         #[rust_name = "scope_cxx"]
         pub(in crate::test_with_scope) fn scope(self: Pin<&mut TestWithScope>) -> *mut Scope;
+
+        type Value;
+        type ParseNode;
+        // We allow dead code because this isn't used in production and we
+        // can't tag things in the bridge with cfg(test).
+        #[allow(dead_code)]
+        pub(in crate::value) fn NewValueForTesting() -> UniquePtr<Value>;
+        pub(in crate::value) fn ValueSize() -> usize;
+        #[cxx_return_type = "Value::Type"]
+        #[cxx_name = "type"]
+        // We can't call this "type" in rust since it's a keyword.
+        pub(in crate::value) fn kind(self: &Value) -> ValueType;
+        pub(in crate::value) fn boolean_value(self: &Value) -> &bool;
+        pub(in crate::value) fn int_value(self: &Value) -> &i64;
+        #[cxx_return_type = "const std::string&"]
+        pub(in crate::value) fn string_value(self: &Value) -> &str;
+        #[cxx_name = "GetValueList"]
+        pub(in crate::value) fn list_value_cxx(val: &Value) -> SliceAny;
+        pub(in crate::value) fn scope_value(self: &Value) -> *const Scope;
+        pub(in crate::value) unsafe fn SetValueNone(val: Pin<&mut Value>, origin: *const ParseNode);
+        pub(in crate::value) unsafe fn SetValueBool(
+            val: Pin<&mut Value>,
+            origin: *const ParseNode,
+            b: bool,
+        );
+        pub(in crate::value) unsafe fn SetValueInt(
+            val: Pin<&mut Value>,
+            origin: *const ParseNode,
+            i: i64,
+        );
+        pub(in crate::value) unsafe fn SetValueString(
+            val: Pin<&mut Value>,
+            origin: *const ParseNode,
+            s: &str,
+        );
+        // Initialises self as a list of `size` elements and returns a pointer to the
+        // start.
+        pub(in crate::value) unsafe fn SetValueList(
+            val: Pin<&mut Value>,
+            origin: *const ParseNode,
+            size: usize,
+        ) -> *mut Any;
+        pub(in crate::value) unsafe fn SetValueScope(
+            val: Pin<&mut Value>,
+            origin: *const ParseNode,
+            scope: UniquePtr<Scope>,
+        );
     }
 }
 
diff --git a/src/gn/starlark/crates/ffi/src/lib.rs b/src/gn/starlark/crates/ffi/src/lib.rs
index d9a9fda..2e698a0 100644
--- a/src/gn/starlark/crates/ffi/src/lib.rs
+++ b/src/gn/starlark/crates/ffi/src/lib.rs
@@ -18,10 +18,17 @@
 //! these types in their own files.
 mod bridge;
 mod label;
+mod mutability;
+mod opaque;
 mod output_file;
 mod scope;
 mod settings;
+mod slice;
 mod test_with_scope;
+mod value;
 
-pub use bridge::{Label, OutputFile, Scope, Settings, SourceDir};
+pub use bridge::{KeyValue, Label, OutputFile, Scope, Settings, SourceDir, Value, ValueType};
+pub use mutability::Immutable;
+pub use opaque::{NonOpaque, OpaqueSized};
+pub use slice::{OwnedSlice, Slice};
 pub use test_with_scope::TestWithScope;
diff --git a/src/gn/starlark/crates/ffi/src/mutability.rs b/src/gn/starlark/crates/ffi/src/mutability.rs
new file mode 100644
index 0000000..46027da
--- /dev/null
+++ b/src/gn/starlark/crates/ffi/src/mutability.rs
@@ -0,0 +1,27 @@
+// Copyright 2026 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::ops::Deref;
+
+/// A wrapper that enforces compile-time immutability for its inner type.
+///
+/// It only implements `Deref`, meaning that once wrapped, callers can only
+/// access read-only methods taking `&self`.
+pub struct Immutable<T>(T);
+
+impl<T> From<T> for Immutable<T> {
+    #[inline(always)]
+    fn from(inner: T) -> Self {
+        Self(inner)
+    }
+}
+
+impl<T> Deref for Immutable<T> {
+    type Target = T;
+
+    #[inline(always)]
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
diff --git a/src/gn/starlark/crates/ffi/src/opaque.rs b/src/gn/starlark/crates/ffi/src/opaque.rs
new file mode 100644
index 0000000..3676c2d
--- /dev/null
+++ b/src/gn/starlark/crates/ffi/src/opaque.rs
@@ -0,0 +1,27 @@
+// Copyright 2026 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::pin::Pin;
+
+/// Trait for types that are opaque, but that we need to know the size of.
+///
+/// This is usually because we intend to iterate over arrays of them.
+pub trait OpaqueSized {
+    /// Returns the size of the type in bytes.
+    fn size() -> usize;
+}
+
+impl OpaqueSized for crate::bridge::Value {
+    #[inline(always)]
+    fn size() -> usize {
+        crate::bridge::ValueSize()
+    }
+}
+
+/// Marker trait indicating that a type is not an opaque type.
+pub trait NonOpaque {}
+
+impl<'a> NonOpaque for crate::bridge::KeyValue<'a> {}
+impl<T> NonOpaque for *mut T {}
+impl<T> NonOpaque for Pin<&mut T> {}
diff --git a/src/gn/starlark/crates/ffi/src/scope.rs b/src/gn/starlark/crates/ffi/src/scope.rs
index 8e4c85a..e063d7a 100644
--- a/src/gn/starlark/crates/ffi/src/scope.rs
+++ b/src/gn/starlark/crates/ffi/src/scope.rs
@@ -2,11 +2,45 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-use crate::Scope;
+use std::pin::Pin;
+
+use starlark::values::FrozenValue;
+
+use crate::{bridge::Value, Immutable, OwnedSlice, Scope};
+
 impl Scope {
+    pub(crate) fn new<'b>(
+        parent: &Self,
+        keys: &[&str],
+    ) -> (cxx::UniquePtr<Self>, OwnedSlice<Pin<&'b mut Value>>) {
+        let mut nested_scope = cxx::UniquePtr::<Self>::null();
+        let values = crate::bridge::NewScope(parent, keys, &mut nested_scope);
+        (nested_scope, values.into())
+    }
+
     /// Returns the settings for the given scope.
     pub fn settings(&self) -> &crate::Settings {
         // Safety: Settings pointer is always valid and non-null.
         unsafe { self.settings_cxx().as_ref() }.unwrap()
     }
+
+    /// Returns the items currently in the scope (not including parent scopes).
+    pub fn items(&self) -> Immutable<OwnedSlice<crate::bridge::KeyValue<'_>>> {
+        let slice = crate::bridge::GetScopeItems(self);
+        Immutable::from(crate::OwnedSlice::<crate::bridge::KeyValue>::from(slice))
+    }
+
+    /// Converts Scope items to Starlark key-value pairs.
+    pub fn get_kv<'a>(
+        &'a self,
+        frozen_heap: &starlark::values::FrozenHeap,
+    ) -> Vec<(&'a str, FrozenValue)> {
+        let owned = self.items();
+        let mut items = Vec::new();
+        // Iterate over the KeyValue contiguous slice:
+        for pair in owned.as_slice() {
+            items.push((pair.key, pair.value.to_rust(frozen_heap)));
+        }
+        items
+    }
 }
diff --git a/src/gn/starlark/crates/ffi/src/slice.rs b/src/gn/starlark/crates/ffi/src/slice.rs
new file mode 100644
index 0000000..acc9945
--- /dev/null
+++ b/src/gn/starlark/crates/ffi/src/slice.rs
@@ -0,0 +1,140 @@
+// Copyright 2026 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{
+    ffi::c_void,
+    marker::PhantomData,
+    ops::{Deref, DerefMut},
+    pin::Pin,
+};
+
+use crate::opaque::{NonOpaque, OpaqueSized};
+
+/// A &[T]-like object.
+///
+/// Use this for one of two reasons:
+/// * C++ returns a slice of an opaque type, for which &[T] does not work.
+/// * C++ returns a std::vector<T>, in which case you should use
+///   `OwnedSlice<T>`.
+///
+/// Note that either as_slice or iter is implemented, but not both, depending
+/// on whether T is opaque.
+pub struct Slice<T> {
+    raw: crate::bridge::SliceAny,
+    _marker: PhantomData<T>,
+}
+
+impl<T> From<crate::bridge::SliceAny> for Slice<T> {
+    #[inline(always)]
+    fn from(raw: crate::bridge::SliceAny) -> Self {
+        Self {
+            raw,
+            _marker: PhantomData,
+        }
+    }
+}
+
+impl<T: OpaqueSized> Slice<T> {
+    /// Returns a mutable iterator over the elements of the slice.
+    #[inline(always)]
+    pub fn iter_mut(&mut self) -> impl Iterator<Item = Pin<&mut T>> {
+        let size = T::size();
+        let mut current = self.raw.ptr;
+        (0..self.raw.len).map(move |_| {
+            let ptr = current;
+            current = unsafe { current.add(size) };
+            // Safety: The pointer is valid and exclusive for the lifetime of the iteration.
+            unsafe { Pin::new_unchecked(&mut *ptr.cast::<T>()) }
+        })
+    }
+
+    /// Returns an iterator over the elements of the slice.
+    #[inline(always)]
+    pub fn iter(&self) -> impl Iterator<Item = &T> {
+        let size = T::size();
+        let mut current = self.raw.ptr;
+        (0..self.raw.len).map(move |_| {
+            let ptr = current;
+            current = unsafe { current.add(size) };
+            // Safety: The pointer is valid for the lifetime of Slice.
+            unsafe { &*ptr.cast::<T>() }
+        })
+    }
+}
+
+impl<T: NonOpaque> Slice<T> {
+    /// Returns a view of the slice as a standard mutable rust slice.
+    #[inline(always)]
+    pub fn as_slice_mut(&mut self) -> &mut [T] {
+        if self.raw.len == 0 {
+            &mut []
+        } else {
+            // Safety: T implements NonOpaque, thus guaranteeing its size is correct.
+            // (If T is opaque, rust is lied to and thinks a T is a u8, but &T remains
+            // correct).
+            unsafe { std::slice::from_raw_parts_mut(self.raw.ptr.cast::<T>(), self.raw.len) }
+        }
+    }
+
+    /// Returns a view of the slice as a standard rust slice.
+    #[inline(always)]
+    pub fn as_slice(&self) -> &[T] {
+        if self.raw.len == 0 {
+            &[]
+        } else {
+            // Safety: T implements NonOpaque, thus guaranteeing its size is correct,
+            // and the memory is valid for read access for the lifetime of Slice.
+            unsafe { std::slice::from_raw_parts(self.raw.ptr.cast::<T>(), self.raw.len) }
+        }
+    }
+}
+
+/// A std::vector<T> for which ownership has been papssed to rust.
+///
+/// To create an OwnedSlice, create a std::vector and call `ReleaseVector`
+/// to release ownership of the slice.
+pub struct OwnedSlice<T> {
+    slice: Slice<T>,
+}
+
+impl<T> From<crate::bridge::SliceAny> for OwnedSlice<T> {
+    #[inline(always)]
+    fn from(raw: crate::bridge::SliceAny) -> Self {
+        Self {
+            slice: Slice::from(raw),
+        }
+    }
+}
+
+impl<T> Deref for OwnedSlice<T> {
+    type Target = Slice<T>;
+
+    #[inline(always)]
+    fn deref(&self) -> &Self::Target {
+        &self.slice
+    }
+}
+
+impl<T> DerefMut for OwnedSlice<T> {
+    #[inline(always)]
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.slice
+    }
+}
+
+impl<T> Drop for OwnedSlice<T> {
+    #[inline(always)]
+    fn drop(&mut self) {
+        // We don't write this function, this is the libc free function.
+        extern "C" {
+            fn free(ptr: *mut c_void);
+        }
+
+        // Safety: Calling free is safe. The pointer is guarunteed to be valid and owned
+        // by us.
+        unsafe {
+            free(self.slice.raw.ptr.cast::<c_void>());
+        }
+    }
+}
diff --git a/src/gn/starlark/crates/ffi/src/value.rs b/src/gn/starlark/crates/ffi/src/value.rs
new file mode 100644
index 0000000..95a26c0
--- /dev/null
+++ b/src/gn/starlark/crates/ffi/src/value.rs
@@ -0,0 +1,199 @@
+// Copyright 2026 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::pin::Pin;
+
+use starlark::values::{list::ListRef, structs::StructRef, FrozenValue};
+
+pub use crate::bridge::ParseNode;
+use crate::{
+    bridge::{SliceAny, Value, ValueType},
+    Immutable, Scope, Slice,
+};
+
+impl Value {
+    fn list_value(&self) -> Immutable<Slice<Self>> {
+        Immutable::from(Slice::from(crate::bridge::list_value_cxx(self)))
+    }
+
+    pub fn to_rust(&self, frozen_heap: &starlark::values::FrozenHeap) -> FrozenValue {
+        match self.kind() {
+            ValueType::None => FrozenValue::new_none(),
+            ValueType::Boolean => FrozenValue::new_bool(*self.boolean_value()),
+            ValueType::Integer => frozen_heap.alloc(*self.int_value()),
+            ValueType::String => frozen_heap.alloc(self.string_value()),
+            ValueType::List => {
+                let slice = self.list_value();
+                let items: Vec<_> = slice.iter().map(|item| item.to_rust(frozen_heap)).collect();
+                frozen_heap.alloc(items)
+            },
+            ValueType::Scope => {
+                let scope_ptr = self.scope_value();
+                // Safety: C++ Value invariants guarantee that scope_value() is never null
+                // when the type is SCOPE.
+                let scope = unsafe { &*scope_ptr };
+                frozen_heap.alloc(starlark::values::structs::AllocStruct(
+                    scope.get_kv(frozen_heap),
+                ))
+            },
+            _ => unreachable!(),
+        }
+    }
+
+    pub fn assign<'v>(
+        mut self: Pin<&mut Self>,
+        val: starlark::values::Value<'v>,
+        scope: &mut Scope,
+        origin: *const ParseNode,
+    ) {
+        if val.is_none() {
+            // Safety: Just an FFI function.
+            unsafe {
+                crate::bridge::SetValueNone(self.as_mut(), origin);
+            }
+        } else if let Some(s) = val.unpack_str() {
+            // Safety: Just an FFI function.
+            unsafe {
+                crate::bridge::SetValueString(self.as_mut(), origin, s);
+            }
+        } else if let Some(b) = val.unpack_bool() {
+            // Safety: Just an FFI function.
+            unsafe {
+                crate::bridge::SetValueBool(self.as_mut(), origin, b);
+            }
+        } else if let Some(i) = val.unpack_i32() {
+            // Safety: Just an FFI function.
+            unsafe {
+                crate::bridge::SetValueInt(self.as_mut(), origin, i64::from(i));
+            }
+        } else if let Some(l) = ListRef::from_value(val) {
+            let mut slice: Slice<Self> = SliceAny {
+                // Safety: Just an FFI function.
+                ptr: unsafe { crate::bridge::SetValueList(self.as_mut(), origin, l.len()) },
+                len: l.len(),
+            }
+            .into();
+            for (el_pin, src) in slice.iter_mut().zip(l.iter()) {
+                el_pin.assign(src, scope, origin);
+            }
+        } else if let Some(s) = StructRef::from_value(val) {
+            let keys: Vec<&str> = s.iter().map(|(k, _)| k.as_str()).collect();
+            let (nested_scope, mut values) = Scope::new(scope, &keys);
+
+            for (v_starlark, v_cxx) in s.iter().map(|(_, v)| v).zip(values.as_slice_mut()) {
+                v_cxx.as_mut().assign(v_starlark, scope, origin);
+            }
+
+            // Safety: Just an FFI function.
+            unsafe {
+                crate::bridge::SetValueScope(self.as_mut(), origin, nested_scope);
+            }
+        } else {
+            todo!("Arbitrary starlark values not (yet) supported");
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use starlark::values::FrozenHeap;
+
+    use super::*;
+    use crate::TestWithScope;
+
+    fn back_and_forth<'v>(
+        heap: &FrozenHeap,
+        val: starlark::values::Value<'v>,
+    ) -> starlark::values::Value<'v> {
+        let mut setup = TestWithScope::new();
+        let scope = setup.scope();
+
+        let mut value = crate::bridge::NewValueForTesting();
+        value.pin_mut().assign(val, scope, std::ptr::null());
+        value.to_rust(heap).to_value()
+    }
+
+    #[test]
+    fn test_none_conversion() {
+        let heap = FrozenHeap::new();
+        assert!(back_and_forth(&heap, FrozenValue::new_none().to_value()).is_none());
+    }
+
+    #[test]
+    fn test_bool_conversion() {
+        let heap = FrozenHeap::new();
+        assert_eq!(
+            back_and_forth(&heap, heap.alloc(true).to_value()).unpack_bool(),
+            Some(true)
+        );
+        assert_eq!(
+            back_and_forth(&heap, heap.alloc(false).to_value()).unpack_bool(),
+            Some(false)
+        );
+    }
+
+    #[test]
+    fn test_int_conversion() {
+        let heap = FrozenHeap::new();
+        assert_eq!(
+            back_and_forth(&heap, heap.alloc(123456789i32).to_value()).unpack_i32(),
+            Some(123456789)
+        );
+    }
+
+    #[test]
+    fn test_string_conversion() {
+        let heap = FrozenHeap::new();
+        assert_eq!(
+            back_and_forth(&heap, heap.alloc("hello world").to_value()).unpack_str(),
+            Some("hello world")
+        );
+        assert_eq!(
+            back_and_forth(
+                &heap,
+                heap.alloc("hello long string without SSO optimizations")
+                    .to_value(),
+            )
+            .unpack_str(),
+            Some("hello long string without SSO optimizations")
+        );
+    }
+
+    #[test]
+    fn test_list_conversion() {
+        let heap = FrozenHeap::new();
+        let list_ref = ListRef::from_value(back_and_forth(
+            &heap,
+            heap.alloc(vec![heap.alloc(42), heap.alloc("hello")])
+                .to_value(),
+        ))
+        .unwrap();
+        assert_eq!(list_ref.len(), 2);
+        let mut iter = list_ref.iter();
+        assert_eq!(iter.next().unwrap().unpack_i32(), Some(42));
+        assert_eq!(iter.next().unwrap().unpack_str(), Some("hello"));
+    }
+
+    #[test]
+    fn test_struct_conversion() {
+        let heap = FrozenHeap::new();
+        let struct_ref = StructRef::from_value(back_and_forth(
+            &heap,
+            heap.alloc(starlark::values::structs::AllocStruct(vec![
+                ("foo", heap.alloc(100)),
+                ("bar", heap.alloc("baz")),
+            ]))
+            .to_value(),
+        ))
+        .unwrap();
+        let get_field = |name: &str| {
+            struct_ref
+                .iter()
+                .find(|(k, _)| k.as_str() == name)
+                .map(|(_, v)| v)
+        };
+        assert_eq!(get_field("foo").unwrap().unpack_i32(), Some(100));
+        assert_eq!(get_field("bar").unwrap().unpack_str(), Some("baz"));
+    }
+}
diff --git a/src/gn/value.h b/src/gn/value.h
index d963447..ec4cb5a 100644
--- a/src/gn/value.h
+++ b/src/gn/value.h
@@ -36,7 +36,7 @@
 // Represents a variable value in the interpreter.
 class Value {
  public:
-  enum Type {
+  enum Type : uint8_t {
     NONE = 0,
     BOOLEAN,
     INTEGER,