Browse Source

breaking: completely re-factor structure of reflection system

- divide reflections up into Reflection, Object
- allow getter/setter usage via a Proxy class
- allow easy chaining by making it so you don't need to know the intermediate types
- inject name info metadata into Object
- scaffold for TypeCoercion, allowing us to treat certain types as others
Sam Jaffe 3 years ago
parent
commit
5ccd4ce1d1

+ 0 - 88
include/reflect/reflect.hpp

@@ -1,88 +0,0 @@
-#pragma once
-
-#include <string>
-#include <unordered_map>
-
-template <typename Object>
-class reflection {
-private:
-  template <typename T> using Field = T Object::*;
-  template <typename K, typename V> using map = std::unordered_map<K, V>;
-  
-private:
-  template <typename T> static map<std::string, Field<T>> s_reflector;
-  static map<std::ptrdiff_t, std::string> s_fieldname;
-  
-private:
-  template <typename T>
-  static std::ptrdiff_t offset(Field<T> field) {
-    return std::ptrdiff_t(&(reinterpret_cast<Object const*>(NULL)->*field));
-  }
-  
-  static std::string_view name(std::ptrdiff_t offset) {
-    auto it = s_fieldname.find(offset);
-    if (it == s_fieldname.end()) { return ""; }
-    return it->second;
-  }
-
-public:
-  reflection();
-
-  template <typename T>
-  static bool exists(std::string const & name) {
-    return s_reflector<T>.count(name);
-  }
-
-  template <typename T>
-  static Field<T> get_pointer(std::string const & name) {
-    auto it = s_reflector<T>.find(name);
-    if (it == s_reflector<T>.end()) { return nullptr; }
-    return it->second;
-  }
-  
-  template <typename T>
-  static std::string_view name(Object const * ptr, T const & data) {
-    return name(std::ptrdiff_t(&data) - std::ptrdiff_t(ptr));
-  }
-  
-  template <typename T>
-  static std::string_view name(Object const & obj, T const & data) {
-    return name(&obj, data);
-  }
-  
-  template <typename T>
-  static std::string_view name(Field<T> field) {
-    return name(offset(field));
-  }
-
-  template <typename T>
-  static void bind(Field<T> field, std::string const & name,
-                   std::string const & /*discard*/ = "") {
-    s_fieldname.emplace(offset(field), name);
-    s_reflector<T>.emplace(name, field);
-  }
-};
-
-#define REFLECT_MEMBER_PP_IMPL2(type, field, ...) \
-  bind(&type::field, ##__VA_ARGS__, #field)
-#define REFLECT_MEMBER_PP_IMPL(type, field, ...) \
-  REFLECT_MEMBER_PP_IMPL2(type, field, ##__VA_ARGS__)
-#define UNWRAP(...) __VA_ARGS__
-#define REFLECT_MEMBER_PP2(data, elem) REFLECT_MEMBER_PP_IMPL(data, UNWRAP elem)
-#define REFLECT_MEMBER_PP(r, data, elem) REFLECT_MEMBER_PP2(data, elem);
-
-#include <boost/preprocessor/variadic/to_seq.hpp>
-#include <boost/preprocessor/seq/for_each.hpp>
-#define CONCAT2(A, B) A##B
-#define CONCAT(A, B) CONCAT2(A, B)
-#define CREATE_REFLECTION(type, ...)                             \
-  template <> reflection<type>::reflection() {                   \
-    BOOST_PP_SEQ_FOR_EACH(REFLECT_MEMBER_PP, type,               \
-                          BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__)) \
-  }                                                              \
-  static reflection<type> CONCAT(reflector_, __LINE__) {}
-
-template <typename Object> template <typename T>
-std::unordered_map<std::string, T Object::*> reflection<Object>::s_reflector;
-template <typename Object>
-std::unordered_map<std::ptrdiff_t, std::string> reflection<Object>::s_fieldname;

+ 27 - 0
include/reflection/forward.h

@@ -0,0 +1,27 @@
+//
+//  forward.h
+//  reflection
+//
+//  Created by Sam Jaffe on 7/3/22.
+//  Copyright © 2022 Sam Jaffe. All rights reserved.
+//
+
+#pragma once
+
+#include <functional>
+#include <map>
+#include <string>
+#include <type_traits>
+
+namespace reflection {
+class Object;
+template <typename Obj, typename = void> class Reflection;
+template <typename T> class Proxy;
+template <typename O, typename I> struct TypeConversion;
+
+template <typename Func> using Cache = std::map<std::string_view, Func>;
+template <typename Obj> using Accessor = std::function<Object(Obj &, std::string)>;
+template <typename Obj> using Getter = std::function<Object(Obj const &, std::string)>;
+}
+
+#define reflect(object) reflection::Object(object, #object)

+ 119 - 0
include/reflection/object.h

@@ -0,0 +1,119 @@
+//
+//  object.h
+//  reflection
+//
+//  Created by Sam Jaffe on 7/3/22.
+//  Copyright © 2022 Sam Jaffe. All rights reserved.
+//
+
+#pragma once
+
+#include <stdexcept>
+#include <typeindex>
+#include <utility>
+
+#include "reflection/forward.h"
+
+namespace reflection {
+class Object {
+public:
+  template <typename T> Object(T const &data, std::string name = "this");
+  template <typename T> Object(T &data, std::string name = "this");
+  template <typename T> Object(T &&data, std::string name = "this");
+  template <typename T> Object(Proxy<T> data, std::string name = "this");
+
+  Object own() const { return clone_(*this); }
+
+  std::string_view name() const {
+    return std::string_view(name_).substr(name_.rfind('.') + 1);
+  }
+  std::string_view path() const { return name_; }
+  char const *type() const { return type_.name(); }
+  
+  template <typename T> bool is_a() const { return type_ == typeid(T); }
+
+  template <typename T> operator T &() const & { return cast<T &>(); }
+  template <typename T> operator T const &() const & { return cast<T const &>(); }
+  template <typename T> operator T () && {
+    return is_a<T>() ? std::move(cast<T &>()) : cast<T const &>();
+  }
+
+  Object get(std::string_view id) & { return (this->*get_)(id); }
+  Object get(std::string_view id) const & { return (this->*get_)(id); }
+  Object get(std::string_view id) && { return (this->*get_)(id).own(); }
+
+  template <typename C, typename =
+        std::enable_if_t<std::is_constructible_v<std::string_view,
+                                                 decltype(*std::begin(std::declval<C>()))>>>
+  Object get(C const &container) const {
+    return get(*this, std::begin(container), std::end(container));
+  }
+  
+private:
+  template <typename It> static Object get(Object o, It it, It const end) {
+    for (; it != end; ++it) {
+      o = std::move(o).get(*it);
+    }
+    return o;
+  }
+    
+  template <typename T> T cast() const {
+    // Why can we decay this away?
+    using V = std::remove_const_t<std::remove_reference_t<T>>;
+    // 1) typeid does not respect const
+    if (!is_a<V>()) { throw std::bad_cast(); }
+    // 2) We guard against using mutable-reference on immutable Object here
+    if (std::is_same_v<T, V &> && const_) { throw std::bad_cast(); }
+    // 3) Proxy always contains mutable data
+    if (proxy_) {
+      return T(*std::static_pointer_cast<Proxy<V>>(data_));
+    }
+    // 4) The const will be re-added by type coercion
+    return *std::static_pointer_cast<V>(data_);
+  }
+
+  template <typename T> Object getter(std::string_view id) const;
+  template <typename T> Object accessor(std::string_view id) const;
+  template <typename T> static Object deep(Object const &obj) {
+    return Object(obj.cast<T>(), obj.name_);
+  }
+
+private:
+  std::type_index type_; // 
+  std::string name_; // The property name as a dot-separated path
+  bool proxy_{false}; // Are we using a proxy class to store non-trivial setter behavior
+  bool const_; // Is the object immutable (const-ref or rvalue)
+  std::shared_ptr<void> data_;
+  Object (Object::*get_)(std::string_view) const;
+  Object (*clone_)(Object const &) = [](Object const &obj) { return obj; };
+};
+}
+
+#include "reflection/reflection.h"
+
+namespace reflection {
+template <typename T> Object Object::getter(std::string_view id) const {
+  return Reflection<T>::getter(id)(cast<T const &>(), name_ + "." + std::string(id));
+}
+
+template <typename T> Object Object::accessor(std::string_view id) const {
+  return Reflection<T>::accessor(id)(cast<T &>(), name_ + "." + std::string(id));
+}
+
+template <typename T> Object::Object(T const &data, std::string name)
+    : type_(typeid(T)), name_(std::move(name)), const_(true),
+      data_(const_cast<T *>(&data), [](auto*){}), get_(&Object::getter<T>) {}
+
+template <typename T> Object::Object(T &data, std::string name)
+    : type_(typeid(T)), name_(std::move(name)), const_(false),  data_(&data, [](auto*){}),
+      get_(&Object::accessor<T>) {}
+
+template <typename T> Object::Object(T &&data, std::string name)
+    : type_(typeid(T)), name_(std::move(name)), const_(true),
+      data_(std::make_shared<T>(std::move(data))), get_(&Object::getter<T>),
+      clone_(&Object::deep<T>) {}
+
+template <typename T> Object::Object(Proxy<T> data, std::string name)
+    : type_(typeid(T)), name_(std::move(name)), proxy_(true), const_(false),
+      data_(std::make_shared<Proxy<T>>(std::move(data))), get_(&Object::getter<T>) {}
+}

+ 30 - 0
include/reflection/proxy.h

@@ -0,0 +1,30 @@
+//
+//  proxy.h
+//  reflection
+//
+//  Created by Sam Jaffe on 7/3/22.
+//  Copyright © 2022 Sam Jaffe. All rights reserved.
+//
+
+#pragma once
+
+#include <functional>
+
+#include "reflection/forward.h"
+
+namespace reflection {
+template <typename T> class Proxy {
+public:
+  template <typename O> Proxy(O &obj, T (O::*get)() const, void (O::*set)(T))
+      : data_((obj.*get)()), set_([&obj, set](auto &v) { (obj.*set)(v); }) {}
+
+  ~Proxy() { set_(data_); }
+
+  operator T &() { return data_; }
+
+private:
+  T data_;
+  std::function<void(T const&)> set_;
+};
+}
+

+ 92 - 0
include/reflection/reflection.h

@@ -0,0 +1,92 @@
+#pragma once
+
+#include <functional>
+#include <stdexcept>
+#include <string>
+#include <type_traits>
+#include <utility>
+
+#include "reflection/forward.h"
+#include "reflection/object.h"
+#include "reflection/proxy.h"
+
+#define REFLECTION(expr) [=](Obj &obj, std::string name) { return Object(expr, std::move(name)); }
+#define CONST_REFLECTION(expr) [=](Obj const &obj, std::string name) { return Object(expr, std::move(name)); }
+
+namespace reflection {
+
+template <typename T>
+constexpr auto is_final_reflection =
+    std::is_same_v<T, std::string> || std::is_fundamental_v<T>;
+
+template <typename Obj, typename> class Reflection {
+public:
+  operator bool() const { return true; }
+  
+  static Getter<Obj> getter(std::string_view id) {
+    if (auto it = const_members_.find(id); it != const_members_.end()) {
+      return it->second;
+    }
+    throw std::out_of_range("no id in reflection");
+  }
+
+  static Accessor<Obj> accessor(std::string_view id) {
+    if (auto it = members_.find(id); it != members_.end()) {
+      return it->second;
+    }
+    throw std::out_of_range("no id in reflection");
+  }
+
+  template <typename T, typename V>
+  Reflection &bind(std::string_view id, T (Obj::*get)() const, void (Obj::*set)(V)) {
+    members_.emplace(id, REFLECTION(Proxy<T>(obj, get, set)));
+    const_members_.emplace(id, CONST_REFLECTION((obj.*get)()));
+    return *this;
+  }
+
+  template <typename T>
+  Reflection &bind(std::string_view id, T Obj::*member) {
+    members_.emplace(id, REFLECTION(obj.*member));
+    const_members_.emplace(id, CONST_REFLECTION(obj.*member));
+    return *this;
+  }
+
+  template <typename R, typename T>
+  Reflection &bind(std::string_view id, T Obj::*member) {
+    TypeConversion<R, T> convert;
+    members_.emplace(id, CONST_REFLECTION(convert(obj.*member)));
+    const_members_.emplace(id, CONST_REFLECTION(convert(obj.*member)));
+    return *this;
+  }
+
+private:
+  static Cache<Accessor<Obj>> members_;
+  static Cache<Getter<Obj>> const_members_;
+};
+
+template <typename T> class Reflection<T, std::enable_if_t<is_final_reflection<T>>> {
+public:
+  static Getter<T> getter(std::string_view) { throw std::logic_error("unreflectable"); }
+  static Accessor<T> accessor(std::string_view) { throw std::logic_error("unreflectable"); }
+};
+
+template <typename T> class Reflection<T*> {
+public:
+  static auto getter(std::string_view id) {
+    return [f = Reflection<T>::getter(id)](T const *ptr, std::string name) {
+      return f(*ptr, std::move(name));
+    };
+  }
+
+  static auto accessor(std::string_view id) {
+    return [f = Reflection<T>::accessor(id)](T *ptr, std::string name) {
+      return f(*ptr, std::move(name));
+    };
+  }
+};
+
+template <typename Obj, typename Void>
+Cache<Accessor<Obj>> Reflection<Obj, Void>::members_;
+template <typename Obj, typename Void>
+Cache<Getter<Obj>> Reflection<Obj, Void>::const_members_;
+}

+ 143 - 11
reflection.xcodeproj/project.pbxproj

@@ -7,8 +7,10 @@
 	objects = {
 
 /* Begin PBXBuildFile section */
-		CD2FF9072310BE8500ABA548 /* reflect_test.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CD5535251EEC689700108F81 /* reflect_test.cpp */; };
+		CD2FF9072310BE8500ABA548 /* reflection_test.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CD5535251EEC689700108F81 /* reflection_test.cxx */; };
 		CD2FF9092310BE9200ABA548 /* GoogleMock.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CD2FF8F32310BE6D00ABA548 /* GoogleMock.framework */; };
+		CDA9561B28726949006ACEC2 /* reflection in Headers */ = {isa = PBXBuildFile; fileRef = CDA95611287266E5006ACEC2 /* reflection */; settings = {ATTRIBUTES = (Public, ); }; };
+		CDA9561E28731972006ACEC2 /* object_test.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CDA9561D28731972006ACEC2 /* object_test.cxx */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -43,11 +45,18 @@
 /* End PBXContainerItemProxy section */
 
 /* Begin PBXFileReference section */
-		CD2FF8EA2310BE4E00ABA548 /* reflect */ = {isa = PBXFileReference; lastKnownFileType = folder; name = reflect; path = include/reflect; sourceTree = "<group>"; };
 		CD2FF8EB2310BE6D00ABA548 /* GoogleMock.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = GoogleMock.xcodeproj; path = "../../../../gmock-xcode-master/GoogleMock.xcodeproj"; sourceTree = "<group>"; };
 		CD2FF8FE2310BE7C00ABA548 /* reflection-test.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "reflection-test.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
 		CD2FF9032310BE7C00ABA548 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
-		CD5535251EEC689700108F81 /* reflect_test.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = reflect_test.cpp; sourceTree = "<group>"; };
+		CD5535251EEC689700108F81 /* reflection_test.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = reflection_test.cxx; sourceTree = "<group>"; };
+		CDA9560A28726665006ACEC2 /* reflection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = reflection.h; sourceTree = "<group>"; };
+		CDA9560F287266C9006ACEC2 /* forward.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = forward.h; sourceTree = "<group>"; };
+		CDA95610287266CE006ACEC2 /* object.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = object.h; sourceTree = "<group>"; };
+		CDA95611287266E5006ACEC2 /* reflection */ = {isa = PBXFileReference; lastKnownFileType = folder; name = reflection; path = include/reflection; sourceTree = "<group>"; };
+		CDA9561228726723006ACEC2 /* proxy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = proxy.h; sourceTree = "<group>"; };
+		CDA9561728726941006ACEC2 /* libreflection.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libreflection.a; sourceTree = BUILT_PRODUCTS_DIR; };
+		CDA9561C28726A68006ACEC2 /* xcode_gtest_helper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = xcode_gtest_helper.h; sourceTree = "<group>"; };
+		CDA9561D28731972006ACEC2 /* object_test.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = object_test.cxx; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -59,6 +68,13 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
+		CDA9561528726941006ACEC2 /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
 /* End PBXFrameworksBuildPhase section */
 
 /* Begin PBXGroup section */
@@ -77,6 +93,7 @@
 			isa = PBXGroup;
 			children = (
 				CD2FF8FE2310BE7C00ABA548 /* reflection-test.xctest */,
+				CDA9561728726941006ACEC2 /* libreflection.a */,
 			);
 			name = Products;
 			sourceTree = "<group>";
@@ -100,7 +117,8 @@
 			isa = PBXGroup;
 			children = (
 				CD2FF8EB2310BE6D00ABA548 /* GoogleMock.xcodeproj */,
-				CD2FF8EA2310BE4E00ABA548 /* reflect */,
+				CDA95611287266E5006ACEC2 /* reflection */,
+				CDA9560828726665006ACEC2 /* include */,
 				CD5535241EEC683400108F81 /* test */,
 				CD2FF9002310BE7C00ABA548 /* reflection-test */,
 				CD2FF8FF2310BE7C00ABA548 /* Products */,
@@ -111,13 +129,45 @@
 		CD5535241EEC683400108F81 /* test */ = {
 			isa = PBXGroup;
 			children = (
-				CD5535251EEC689700108F81 /* reflect_test.cpp */,
+				CD5535251EEC689700108F81 /* reflection_test.cxx */,
+				CDA9561D28731972006ACEC2 /* object_test.cxx */,
+				CDA9561C28726A68006ACEC2 /* xcode_gtest_helper.h */,
 			);
 			path = test;
 			sourceTree = "<group>";
 		};
+		CDA9560828726665006ACEC2 /* include */ = {
+			isa = PBXGroup;
+			children = (
+				CDA9560928726665006ACEC2 /* reflection */,
+			);
+			path = include;
+			sourceTree = "<group>";
+		};
+		CDA9560928726665006ACEC2 /* reflection */ = {
+			isa = PBXGroup;
+			children = (
+				CDA9560A28726665006ACEC2 /* reflection.h */,
+				CDA9560F287266C9006ACEC2 /* forward.h */,
+				CDA9561228726723006ACEC2 /* proxy.h */,
+				CDA95610287266CE006ACEC2 /* object.h */,
+			);
+			path = reflection;
+			sourceTree = "<group>";
+		};
 /* End PBXGroup section */
 
+/* Begin PBXHeadersBuildPhase section */
+		CDA9561328726941006ACEC2 /* Headers */ = {
+			isa = PBXHeadersBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				CDA9561B28726949006ACEC2 /* reflection in Headers */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXHeadersBuildPhase section */
+
 /* Begin PBXNativeTarget section */
 		CD2FF8FD2310BE7C00ABA548 /* reflection-test */ = {
 			isa = PBXNativeTarget;
@@ -136,19 +186,40 @@
 			productReference = CD2FF8FE2310BE7C00ABA548 /* reflection-test.xctest */;
 			productType = "com.apple.product-type.bundle.unit-test";
 		};
+		CDA9561628726941006ACEC2 /* reflection */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = CDA9561828726941006ACEC2 /* Build configuration list for PBXNativeTarget "reflection" */;
+			buildPhases = (
+				CDA9561328726941006ACEC2 /* Headers */,
+				CDA9561428726941006ACEC2 /* Sources */,
+				CDA9561528726941006ACEC2 /* Frameworks */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+			);
+			name = reflection;
+			productName = reflection;
+			productReference = CDA9561728726941006ACEC2 /* libreflection.a */;
+			productType = "com.apple.product-type.library.static";
+		};
 /* End PBXNativeTarget section */
 
 /* Begin PBXProject section */
 		CD5535191EEC681E00108F81 /* Project object */ = {
 			isa = PBXProject;
 			attributes = {
-				LastUpgradeCheck = 1030;
+				LastUpgradeCheck = 1230;
 				ORGANIZATIONNAME = "Sam Jaffe";
 				TargetAttributes = {
 					CD2FF8FD2310BE7C00ABA548 = {
 						CreatedOnToolsVersion = 10.3;
 						ProvisioningStyle = Automatic;
 					};
+					CDA9561628726941006ACEC2 = {
+						CreatedOnToolsVersion = 13.4.1;
+						ProvisioningStyle = Automatic;
+					};
 				};
 			};
 			buildConfigurationList = CD55351C1EEC681E00108F81 /* Build configuration list for PBXProject "reflection" */;
@@ -171,6 +242,7 @@
 			projectRoot = "";
 			targets = (
 				CD2FF8FD2310BE7C00ABA548 /* reflection-test */,
+				CDA9561628726941006ACEC2 /* reflection */,
 			);
 		};
 /* End PBXProject section */
@@ -221,7 +293,15 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				CD2FF9072310BE8500ABA548 /* reflect_test.cpp in Sources */,
+				CDA9561E28731972006ACEC2 /* object_test.cxx in Sources */,
+				CD2FF9072310BE8500ABA548 /* reflection_test.cxx in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		CDA9561428726941006ACEC2 /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -233,7 +313,7 @@
 			buildSettings = {
 				CLANG_ANALYZER_NONNULL = YES;
 				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
-				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+				CLANG_CXX_LANGUAGE_STANDARD = "c++17";
 				CLANG_ENABLE_OBJC_WEAK = YES;
 				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
 				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
@@ -257,7 +337,7 @@
 			buildSettings = {
 				CLANG_ANALYZER_NONNULL = YES;
 				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
-				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+				CLANG_CXX_LANGUAGE_STANDARD = "c++17";
 				CLANG_ENABLE_OBJC_WEAK = YES;
 				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
 				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
@@ -280,7 +360,7 @@
 			buildSettings = {
 				ALWAYS_SEARCH_USER_PATHS = NO;
 				CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
-				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+				CLANG_CXX_LANGUAGE_STANDARD = "c++17";
 				CLANG_CXX_LIBRARY = "libc++";
 				CLANG_ENABLE_MODULES = YES;
 				CLANG_ENABLE_OBJC_ARC = YES;
@@ -298,6 +378,7 @@
 				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
 				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
 				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
 				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
 				CLANG_WARN_STRICT_PROTOTYPES = YES;
 				CLANG_WARN_SUSPICIOUS_MOVE = YES;
@@ -333,7 +414,7 @@
 			buildSettings = {
 				ALWAYS_SEARCH_USER_PATHS = NO;
 				CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
-				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+				CLANG_CXX_LANGUAGE_STANDARD = "c++17";
 				CLANG_CXX_LIBRARY = "libc++";
 				CLANG_ENABLE_MODULES = YES;
 				CLANG_ENABLE_OBJC_ARC = YES;
@@ -351,6 +432,7 @@
 				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
 				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
 				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
 				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
 				CLANG_WARN_STRICT_PROTOTYPES = YES;
 				CLANG_WARN_SUSPICIOUS_MOVE = YES;
@@ -374,6 +456,47 @@
 			};
 			name = Release;
 		};
+		CDA9561928726941006ACEC2 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+				CLANG_CXX_LANGUAGE_STANDARD = "c++17";
+				CLANG_ENABLE_OBJC_WEAK = YES;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+				CODE_SIGN_STYLE = Automatic;
+				EXECUTABLE_PREFIX = lib;
+				GCC_C_LANGUAGE_STANDARD = gnu11;
+				MACOSX_DEPLOYMENT_TARGET = 12.0;
+				MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+				MTL_FAST_MATH = YES;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SDKROOT = macosx;
+				SKIP_INSTALL = YES;
+			};
+			name = Debug;
+		};
+		CDA9561A28726941006ACEC2 /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+				CLANG_CXX_LANGUAGE_STANDARD = "c++17";
+				CLANG_ENABLE_OBJC_WEAK = YES;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+				CODE_SIGN_STYLE = Automatic;
+				EXECUTABLE_PREFIX = lib;
+				GCC_C_LANGUAGE_STANDARD = gnu11;
+				MACOSX_DEPLOYMENT_TARGET = 12.0;
+				MTL_FAST_MATH = YES;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SDKROOT = macosx;
+				SKIP_INSTALL = YES;
+			};
+			name = Release;
+		};
 /* End XCBuildConfiguration section */
 
 /* Begin XCConfigurationList section */
@@ -395,6 +518,15 @@
 			defaultConfigurationIsVisible = 0;
 			defaultConfigurationName = Release;
 		};
+		CDA9561828726941006ACEC2 /* Build configuration list for PBXNativeTarget "reflection" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				CDA9561928726941006ACEC2 /* Debug */,
+				CDA9561A28726941006ACEC2 /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
 /* End XCConfigurationList section */
 	};
 	rootObject = CD5535191EEC681E00108F81 /* Project object */;

+ 28 - 11
reflection.xcodeproj/xcshareddata/xcschemes/reflection-test.xcscheme

@@ -1,25 +1,33 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <Scheme
-   LastUpgradeVersion = "1110"
+   LastUpgradeVersion = "1340"
    version = "1.3">
    <BuildAction
       parallelizeBuildables = "YES"
       buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "CDA9561628726941006ACEC2"
+               BuildableName = "libreflection.a"
+               BlueprintName = "reflection"
+               ReferencedContainer = "container:reflection.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
    </BuildAction>
    <TestAction
       buildConfiguration = "Debug"
       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
-      shouldUseLaunchSchemeArgsEnv = "YES">
-      <CodeCoverageTargets>
-         <BuildableReference
-            BuildableIdentifier = "primary"
-            BlueprintIdentifier = "CD2FF8FD2310BE7C00ABA548"
-            BuildableName = "reflection-test.xctest"
-            BlueprintName = "reflection-test"
-            ReferencedContainer = "container:reflection.xcodeproj">
-         </BuildableReference>
-      </CodeCoverageTargets>
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      codeCoverageEnabled = "YES">
       <Testables>
          <TestableReference
             skipped = "NO">
@@ -50,6 +58,15 @@
       savedToolIdentifier = ""
       useCustomWorkingDirectory = "NO"
       debugDocumentVersioning = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "CDA9561628726941006ACEC2"
+            BuildableName = "libreflection.a"
+            BlueprintName = "reflection"
+            ReferencedContainer = "container:reflection.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
    </ProfileAction>
    <AnalyzeAction
       buildConfiguration = "Debug">

+ 129 - 0
test/object_test.cxx

@@ -0,0 +1,129 @@
+//
+//  object_test.cxx
+//  reflection-test
+//
+//  Created by Sam Jaffe on 7/4/22.
+//  Copyright © 2022 Sam Jaffe. All rights reserved.
+//
+
+#include "reflection/object.h"
+
+#include "xcode_gtest_helper.h"
+
+using reflection::Object;
+using reflection::Reflection;
+
+using testing::Eq;
+using testing::NotNull;
+
+namespace {
+struct Example {
+  int a;
+  
+  int get_c() const { return a + 4; }
+  void set_c(int c) { a = c - 4; }
+};
+
+struct Compound {
+  Example ex;
+};
+
+bool _1 = Reflection<Example>()
+    .bind("a", &Example::a)
+    .bind("c", &Example::get_c, &Example::set_c);
+
+bool _2 = Reflection<Compound>().bind("ex", &Compound::ex);
+}
+
+TEST(ObjectTest, CanFetchData) {
+  Example ex{.a = 1};
+  
+  Object a = Object(ex).get("a");
+  
+  EXPECT_NO_THROW((void) int(a)) << a.type();
+  EXPECT_THAT(int(a), 1);
+}
+
+TEST(ObjectTest, CanSetDataOnNonConst) {
+  Example ex{.a = 1};
+  
+  {
+    Object a = Object(ex).get("a");
+    EXPECT_NO_THROW((void) static_cast<int &>(a)) << a.type();
+    static_cast<int &>(a) = 2;
+  }
+  EXPECT_THAT(ex.a, 2);
+}
+
+TEST(ObjectTest, ThrowsWhenTryingToEditConst) {
+  Example const ex{.a = 1};
+  
+  Object a = Object(ex).get("a");
+  EXPECT_THROW((void) static_cast<int &>(a), std::bad_cast);
+}
+
+TEST(ObjectTest, ThrowsWhenTryingToEditMoved) {
+  Object a = Object(Example{.a = 1}).get("a");
+  EXPECT_THROW((void) static_cast<int &>(a), std::bad_cast);
+}
+
+TEST(ObjectTest, ThrowsWhenPerformingTypeCoersion) {
+  Example const ex{.a = 1};
+  
+  Object a = Object(ex).get("a");
+  EXPECT_THROW((void) double(a), std::bad_cast);
+}
+
+TEST(ObjectTest, CanFetchWithGetter) {
+  Example ex{.a = 1};
+  
+  Object c = Object(ex).get("c");
+  EXPECT_NO_THROW((void) int(c)) << c.type();
+  EXPECT_THAT(int(c), Eq(5));
+  static_cast<int &>(c) = 4;
+}
+
+TEST(ObjectTest, CanModifyWithSetter) {
+  Example ex{.a = 1};
+  
+  {
+    Object c = Object(ex).get("c");
+    EXPECT_NO_THROW((void) static_cast<int &>(c)) << c.type();
+    static_cast<int &>(c) = 4;
+    // Notice that the setter is scoped on the Object expiring
+    EXPECT_THAT(ex.a, Eq(1));
+  }
+  
+  EXPECT_THAT(ex.a, Eq(0));
+}
+
+TEST(ObjectTest, ThrowsWhenUsingSetterOnConst) {
+  Example const ex{.a = 1};
+  
+  Object c = Object(ex).get("c");
+  EXPECT_THROW((void) static_cast<int &>(c), std::bad_cast);
+}
+
+TEST(ObjectTest, CanDive) {
+  Compound cm{.ex = {.a = 2}};
+  
+  Object a = Object(cm).get(std::vector{"ex", "a"});
+  EXPECT_NO_THROW((void) int(a));
+  EXPECT_THAT(int(a), Eq(2));
+}
+
+TEST(ObjectTest, AttachesNameInfo) {
+  Compound cm{.ex = {.a = 2}};
+  
+  Object a = Object(cm).get(std::vector{"ex", "a"});
+  EXPECT_THAT(a.path(), Eq("this.ex.a"));
+  EXPECT_THAT(a.name(), Eq("a"));
+}
+
+TEST(ObjectTest, CanReflectVariableName) {
+  Compound cm{.ex = {.a = 2}};
+  
+  Object a = reflect(cm).get(std::vector{"ex", "a"});
+  EXPECT_THAT(a.path(), Eq("cm.ex.a"));
+  EXPECT_THAT(a.name(), Eq("a"));
+}

+ 0 - 48
test/reflect_test.cpp

@@ -1,48 +0,0 @@
-#include "reflect/reflect.hpp"
-
-#include <gmock/gmock.h>
-
-struct example {
-  int a;
-  int b;
-};
-
-CREATE_REFLECTION(example, (a), (b, "c"));
-
-using testing::Eq;
-using testing::NotNull;
-
-TEST(ReflectionTest, BindsParameterByName) {
-  EXPECT_TRUE(reflection<example>::exists<int>("a"));
-}
-
-TEST(ReflectionTest, CanRenameParameter) {
-  EXPECT_FALSE(reflection<example>::exists<int>("b"));
-  EXPECT_TRUE(reflection<example>::exists<int>("c"));
-}
-
-TEST(ReflectionTest, CanAccessMember) {
-  auto p = reflection<example>::get_pointer<int>("a");
-  EXPECT_THAT(p, NotNull());
-  EXPECT_THAT(p, Eq(&example::a));
-
-  example ex = { 5, 6 };
-  EXPECT_THAT(&(ex.*p), Eq(&ex.a));
-  EXPECT_THAT(ex.*p, Eq(5));
-}
-
-TEST(ReflectionTest, CanGetNameFromMemPtr) {
-  EXPECT_THAT(reflection<example>::name(&example::a), Eq("a"));
-  EXPECT_THAT(reflection<example>::name(&example::b), Eq("c"));
-}
-
-TEST(ReflectionTest, CanGetNameFromThis) {
-  example ex = { 5, 6 };
-  EXPECT_THAT(reflection<example>::name(ex, ex.a), Eq("a"));
-  EXPECT_THAT(reflection<example>::name(ex, ex.b), Eq("c"));
-}
-
-TEST(ReflectionTest, NameForMissingIsEmpty) {
-  example ex = { 5, 6 };
-  EXPECT_THAT(reflection<example>::name(ex, 5), Eq(""));
-}

+ 81 - 0
test/reflection_test.cxx

@@ -0,0 +1,81 @@
+#include "reflection/reflection.h"
+
+#include "xcode_gtest_helper.h"
+
+using reflection::Object;
+using reflection::Reflection;
+
+using testing::Eq;
+using testing::NotNull;
+
+namespace {
+struct Example {
+  int a;
+  int b;
+};
+
+bool _ = Reflection<Example>()
+    .bind("a", &Example::a)
+    .bind("c", &Example::b);
+}
+
+TEST(ReflectionTest, AquiresGetter) {
+  EXPECT_NO_THROW(Reflection<Example>::getter("a"));
+}
+
+TEST(ReflectionTest, AquiresAccessor) {
+  EXPECT_NO_THROW(Reflection<Example>::accessor("a"));
+}
+
+TEST(ReflectionTest, ThrowsOnFakeGetter) {
+  EXPECT_THROW(Reflection<Example>::getter("b"), std::out_of_range);
+}
+
+TEST(ReflectionTest, ThrowsOnFakeAccessor) {
+  EXPECT_THROW(Reflection<Example>::accessor("b"), std::out_of_range);
+}
+
+TEST(ReflectionTest, UseOnPrimitiveThrows) {
+  EXPECT_THROW(Reflection<int>::getter("a"), std::logic_error);
+  EXPECT_THROW(Reflection<int>::accessor("a"), std::logic_error);
+}
+
+TEST(ReflectionTest, UseOnStringThrows) {
+  EXPECT_THROW(Reflection<std::string>::getter("a"), std::logic_error);
+  EXPECT_THROW(Reflection<std::string>::accessor("a"), std::logic_error);
+}
+
+//TEST(ReflectionTest, BindsParameterByName) {
+//  EXPECT_TRUE(reflection<example>::exists<int>("a"));
+//}
+//
+//TEST(ReflectionTest, CanRenameParameter) {
+//  EXPECT_FALSE(reflection<example>::exists<int>("b"));
+//  EXPECT_TRUE(reflection<example>::exists<int>("c"));
+//}
+//
+//TEST(ReflectionTest, CanAccessMember) {
+//  auto p = reflection<example>::get_pointer<int>("a");
+//  EXPECT_THAT(p, NotNull());
+//  EXPECT_THAT(p, Eq(&example::a));
+//
+//  example ex = { 5, 6 };
+//  EXPECT_THAT(&(ex.*p), Eq(&ex.a));
+//  EXPECT_THAT(ex.*p, Eq(5));
+//}
+//
+//TEST(ReflectionTest, CanGetNameFromMemPtr) {
+//  EXPECT_THAT(reflection<example>::name(&example::a), Eq("a"));
+//  EXPECT_THAT(reflection<example>::name(&example::b), Eq("c"));
+//}
+//
+//TEST(ReflectionTest, CanGetNameFromThis) {
+//  example ex = { 5, 6 };
+//  EXPECT_THAT(reflection<example>::name(ex, ex.a), Eq("a"));
+//  EXPECT_THAT(reflection<example>::name(ex, ex.b), Eq("c"));
+//}
+//
+//TEST(ReflectionTest, NameForMissingIsEmpty) {
+//  example ex = { 5, 6 };
+//  EXPECT_THAT(reflection<example>::name(ex, 5), Eq(""));
+//}

+ 39 - 0
test/xcode_gtest_helper.h

@@ -0,0 +1,39 @@
+//
+//  xcode_gtest_helper.h
+//  tax-calculator-test
+//
+//  Created by Sam Jaffe on 11/25/20.
+//  Copyright © 2020 Sam Jaffe. All rights reserved.
+//
+
+#pragma once
+
+#if defined(__APPLE__)
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wquoted-include-in-framework-header"
+#pragma clang diagnostic ignored "-Wcomma"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#pragma clang diagnostic pop
+
+#if defined(TARGET_OS_OSX)
+// This is a hack to allow XCode to properly display failures when running
+// unit tests.
+#undef EXPECT_THAT
+#define EXPECT_THAT ASSERT_THAT
+#undef EXPECT_THROW
+#define EXPECT_THROW ASSERT_THROW
+#undef EXPECT_ANY_THROW
+#define EXPECT_ANY_THROW ASSERT_ANY_THROW
+#undef EXPECT_NO_THROW
+#define EXPECT_NO_THROW ASSERT_NO_THROW
+#undef EXPECT_TRUE
+#define EXPECT_TRUE ASSERT_TRUE
+#undef EXPECT_FALSE
+#define EXPECT_FALSE ASSERT_FALSE
+
+#endif
+#endif