2 * Copyright 2017 Facebook, Inc.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 #include <folly/dynamic.h>
19 #include <folly/Assume.h>
20 #include <folly/Format.h>
21 #include <folly/Hash.h>
22 #include <folly/portability/BitsFunctexcept.h>
26 //////////////////////////////////////////////////////////////////////
28 #define FOLLY_DYNAMIC_DEF_TYPEINFO(T) \
29 constexpr const char* dynamic::TypeInfo<T>::name; \
30 constexpr dynamic::Type dynamic::TypeInfo<T>::type; \
33 FOLLY_DYNAMIC_DEF_TYPEINFO(std::nullptr_t)
34 FOLLY_DYNAMIC_DEF_TYPEINFO(bool)
35 FOLLY_DYNAMIC_DEF_TYPEINFO(std::string)
36 FOLLY_DYNAMIC_DEF_TYPEINFO(dynamic::Array)
37 FOLLY_DYNAMIC_DEF_TYPEINFO(double)
38 FOLLY_DYNAMIC_DEF_TYPEINFO(int64_t)
39 FOLLY_DYNAMIC_DEF_TYPEINFO(dynamic::ObjectImpl)
41 #undef FOLLY_DYNAMIC_DEF_TYPEINFO
43 const char* dynamic::typeName() const {
44 return typeName(type_);
47 TypeError::TypeError(const std::string& expected, dynamic::Type actual)
48 : std::runtime_error(sformat(
49 "TypeError: expected dynamic type `{}', but had type `{}'",
51 dynamic::typeName(actual))) {}
54 const std::string& expected,
55 dynamic::Type actual1,
56 dynamic::Type actual2)
57 : std::runtime_error(sformat(
58 "TypeError: expected dynamic types `{}, but had types `{}' and `{}'",
60 dynamic::typeName(actual1),
61 dynamic::typeName(actual2))) {}
63 TypeError::~TypeError() = default;
65 [[noreturn]] void throwTypeError_(
66 std::string const& expected,
67 dynamic::Type actual) {
68 throw TypeError(expected, actual);
71 [[noreturn]] void throwTypeError_(
72 std::string const& expected,
73 dynamic::Type actual1,
74 dynamic::Type actual2) {
75 throw TypeError(expected, actual1, actual2);
78 // This is a higher-order preprocessor macro to aid going from runtime
79 // types to the compile time type system.
80 #define FB_DYNAMIC_APPLY(type, apply) \
84 apply(std::nullptr_t); \
102 apply(std::string); \
110 bool dynamic::operator<(dynamic const& o) const {
111 if (UNLIKELY(type_ == OBJECT || o.type_ == OBJECT)) {
112 throwTypeError_("object", type_);
114 if (type_ != o.type_) {
115 return type_ < o.type_;
118 #define FB_X(T) return CompareOp<T>::comp(*getAddress<T>(), \
120 FB_DYNAMIC_APPLY(type_, FB_X);
124 bool dynamic::operator==(dynamic const& o) const {
125 if (type() != o.type()) {
126 if (isNumber() && o.isNumber()) {
127 auto& integ = isInt() ? *this : o;
128 auto& doubl = isInt() ? o : *this;
129 return integ.asInt() == doubl.asDouble();
134 #define FB_X(T) return *getAddress<T>() == *o.getAddress<T>();
135 FB_DYNAMIC_APPLY(type_, FB_X);
139 dynamic& dynamic::operator=(dynamic const& o) {
141 if (type_ == o.type_) {
142 #define FB_X(T) *getAddress<T>() = *o.getAddress<T>()
143 FB_DYNAMIC_APPLY(type_, FB_X);
147 #define FB_X(T) new (getAddress<T>()) T(*o.getAddress<T>())
148 FB_DYNAMIC_APPLY(o.type_, FB_X);
156 dynamic& dynamic::operator=(dynamic&& o) noexcept {
158 if (type_ == o.type_) {
159 #define FB_X(T) *getAddress<T>() = std::move(*o.getAddress<T>())
160 FB_DYNAMIC_APPLY(type_, FB_X);
164 #define FB_X(T) new (getAddress<T>()) T(std::move(*o.getAddress<T>()))
165 FB_DYNAMIC_APPLY(o.type_, FB_X);
173 dynamic& dynamic::operator[](dynamic const& k) & {
174 if (!isObject() && !isArray()) {
175 throwTypeError_("object/array", type());
180 auto& obj = get<ObjectImpl>();
181 auto ret = obj.insert({k, nullptr});
182 return ret.first->second;
185 dynamic dynamic::getDefault(const dynamic& k, const dynamic& v) const& {
186 auto& obj = get<ObjectImpl>();
187 auto it = obj.find(k);
188 return it == obj.end() ? v : it->second;
191 dynamic dynamic::getDefault(const dynamic& k, dynamic&& v) const& {
192 auto& obj = get<ObjectImpl>();
193 auto it = obj.find(k);
194 // Avoid clang bug with ternary
195 if (it == obj.end()) {
202 dynamic dynamic::getDefault(const dynamic& k, const dynamic& v) && {
203 auto& obj = get<ObjectImpl>();
204 auto it = obj.find(k);
205 // Avoid clang bug with ternary
206 if (it == obj.end()) {
209 return std::move(it->second);
213 dynamic dynamic::getDefault(const dynamic& k, dynamic&& v) && {
214 auto& obj = get<ObjectImpl>();
215 auto it = obj.find(k);
216 return std::move(it == obj.end() ? v : it->second);
219 const dynamic* dynamic::get_ptr(dynamic const& idx) const& {
220 if (auto* parray = get_nothrow<Array>()) {
222 throwTypeError_("int64", idx.type());
224 if (idx < 0 || idx >= parray->size()) {
227 return &(*parray)[size_t(idx.asInt())];
228 } else if (auto* pobject = get_nothrow<ObjectImpl>()) {
229 auto it = pobject->find(idx);
230 if (it == pobject->end()) {
235 throwTypeError_("object/array", type());
239 [[noreturn]] static void throwOutOfRangeAtMissingKey(dynamic const& idx) {
240 auto msg = sformat("couldn't find key {} in dynamic object", idx.asString());
241 std::__throw_out_of_range(msg.c_str());
244 dynamic const& dynamic::at(dynamic const& idx) const& {
245 if (auto* parray = get_nothrow<Array>()) {
247 throwTypeError_("int64", idx.type());
249 if (idx < 0 || idx >= parray->size()) {
250 std::__throw_out_of_range("out of range in dynamic array");
252 return (*parray)[size_t(idx.asInt())];
253 } else if (auto* pobject = get_nothrow<ObjectImpl>()) {
254 auto it = pobject->find(idx);
255 if (it == pobject->end()) {
256 throwOutOfRangeAtMissingKey(idx);
260 throwTypeError_("object/array", type());
264 std::size_t dynamic::size() const {
265 if (auto* ar = get_nothrow<Array>()) {
268 if (auto* obj = get_nothrow<ObjectImpl>()) {
271 if (auto* str = get_nothrow<std::string>()) {
274 throwTypeError_("array/object", type());
277 dynamic::iterator dynamic::erase(const_iterator first, const_iterator last) {
278 auto& arr = get<Array>();
279 return get<Array>().erase(
280 arr.begin() + (first - arr.begin()),
281 arr.begin() + (last - arr.begin()));
284 std::size_t dynamic::hash() const {
289 throwTypeError_("not null/object/array", type());
291 return std::hash<int64_t>()(getInt());
293 return std::hash<double>()(getDouble());
295 return std::hash<bool>()(getBool());
297 // keep it compatible with FBString
298 const auto& str = getString();
299 return ::folly::hash::fnv32_buf(str.data(), str.size());
302 assume_unreachable();
305 char const* dynamic::typeName(Type t) {
306 #define FB_X(T) return TypeInfo<T>::name
307 FB_DYNAMIC_APPLY(t, FB_X);
311 void dynamic::destroy() noexcept {
312 // This short-circuit speeds up some microbenchmarks.
313 if (type_ == NULLT) {
317 #define FB_X(T) detail::Destroy::destroy(getAddress<T>())
318 FB_DYNAMIC_APPLY(type_, FB_X);
324 //////////////////////////////////////////////////////////////////////