1// Copyright (C) 2020 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#ifndef QJSPRIMITIVEVALUE_H
5#define QJSPRIMITIVEVALUE_H
6
7#include <QtQml/qtqmlglobal.h>
8#include <QtQml/qjsnumbercoercion.h>
9
10#include <QtCore/qstring.h>
11#include <QtCore/qnumeric.h>
12#include <QtCore/qvariant.h>
13
14#include <variant>
15#include <cmath>
16
17QT_BEGIN_NAMESPACE
18
19namespace QV4 { struct ExecutionEngine; }
20
21struct QJSPrimitiveUndefined {};
22struct QJSPrimitiveNull {};
23
24class QJSPrimitiveValue
25{
26 template<typename Concrete>
27 struct StringNaNOperators
28 {
29 static constexpr double op(const QString &, QJSPrimitiveUndefined)
30 {
31 return std::numeric_limits<double>::quiet_NaN();
32 }
33
34 static constexpr double op(QJSPrimitiveUndefined, const QString &)
35 {
36 return std::numeric_limits<double>::quiet_NaN();
37 }
38
39 static double op(const QString &lhs, QJSPrimitiveNull) { return op(lhs, 0); }
40 static double op(QJSPrimitiveNull, const QString &rhs) { return op(0, rhs); }
41
42 template<typename T>
43 static double op(const QString &lhs, T rhs)
44 {
45 return Concrete::op(fromString(string: lhs).toDouble(), rhs);
46 }
47
48 template<typename T>
49 static double op(T lhs, const QString &rhs)
50 {
51 return Concrete::op(lhs, fromString(string: rhs).toDouble());
52 }
53
54 static double op(const QString &lhs, const QString &rhs)
55 {
56 return Concrete::op(fromString(string: lhs).toDouble(), fromString(string: rhs).toDouble());
57 }
58 };
59
60 struct AddOperators {
61 static constexpr double op(double lhs, double rhs) { return lhs + rhs; }
62 static bool opOverflow(int lhs, int rhs, int *result)
63 {
64 return qAddOverflow(v1: lhs, v2: rhs, r: result);
65 }
66
67 template<typename T>
68 static QString op(const QString &lhs, T rhs)
69 {
70 return lhs + QJSPrimitiveValue(rhs).toString();
71 }
72
73 template<typename T>
74 static QString op(T lhs, const QString &rhs)
75 {
76 return QJSPrimitiveValue(lhs).toString() + rhs;
77 }
78
79 static QString op(const QString &lhs, const QString &rhs) { return lhs + rhs; }
80 };
81
82 struct SubOperators : private StringNaNOperators<SubOperators> {
83 static constexpr double op(double lhs, double rhs) { return lhs - rhs; }
84 static bool opOverflow(int lhs, int rhs, int *result)
85 {
86 return qSubOverflow(v1: lhs, v2: rhs, r: result);
87 }
88
89 using StringNaNOperators::op;
90 };
91
92 struct MulOperators : private StringNaNOperators<MulOperators> {
93 static constexpr double op(double lhs, double rhs) { return lhs * rhs; }
94 static bool opOverflow(int lhs, int rhs, int *result)
95 {
96 // compare mul_int32 in qv4math_p.h
97 auto hadOverflow = qMulOverflow(v1: lhs, v2: rhs, r: result);
98 if (((lhs < 0) ^ (rhs < 0)) && (*result == 0))
99 return true; // result must be negative 0, does not fit into int
100 return hadOverflow;
101 }
102
103 using StringNaNOperators::op;
104 };
105
106 struct DivOperators : private StringNaNOperators<DivOperators> {
107 static constexpr double op(double lhs, double rhs) { return lhs / rhs; }
108 static constexpr bool opOverflow(int, int, int *)
109 {
110 return true;
111 }
112
113 using StringNaNOperators::op;
114 };
115
116public:
117 enum Type : quint8 {
118 Undefined,
119 Null,
120 Boolean,
121 Integer,
122 Double,
123 String
124 };
125
126 constexpr Type type() const { return Type(d.type()); }
127
128 // Prevent casting from Type to int
129 QJSPrimitiveValue(Type) = delete;
130
131 Q_IMPLICIT constexpr QJSPrimitiveValue() noexcept = default;
132 Q_IMPLICIT constexpr QJSPrimitiveValue(QJSPrimitiveUndefined undefined) noexcept : d(undefined) {}
133 Q_IMPLICIT constexpr QJSPrimitiveValue(QJSPrimitiveNull null) noexcept : d(null) {}
134 Q_IMPLICIT constexpr QJSPrimitiveValue(bool value) noexcept : d(value) {}
135 Q_IMPLICIT constexpr QJSPrimitiveValue(int value) noexcept : d(value) {}
136 Q_IMPLICIT constexpr QJSPrimitiveValue(double value) noexcept : d(value) {}
137 Q_IMPLICIT QJSPrimitiveValue(QString string) noexcept : d(std::move(string)) {}
138
139 explicit QJSPrimitiveValue(const QMetaType type, const void *value) noexcept
140 {
141 switch (type.id()) {
142 case QMetaType::UnknownType:
143 d = QJSPrimitiveUndefined();
144 break;
145 case QMetaType::Nullptr:
146 d = QJSPrimitiveNull();
147 break;
148 case QMetaType::Bool:
149 d = *static_cast<const bool *>(value);
150 break;
151 case QMetaType::Int:
152 d = *static_cast<const int *>(value);
153 break;
154 case QMetaType::Double:
155 d = *static_cast<const double *>(value);
156 break;
157 case QMetaType::QString:
158 d = *static_cast<const QString *>(value);
159 break;
160 default:
161 // Unsupported. Remains undefined.
162 break;
163 }
164 }
165
166 explicit QJSPrimitiveValue(QMetaType type) noexcept
167 {
168 switch (type.id()) {
169 case QMetaType::UnknownType:
170 d = QJSPrimitiveUndefined();
171 break;
172 case QMetaType::Nullptr:
173 d = QJSPrimitiveNull();
174 break;
175 case QMetaType::Bool:
176 d = false;
177 break;
178 case QMetaType::Int:
179 d = 0;
180 break;
181 case QMetaType::Double:
182 d = 0.0;
183 break;
184 case QMetaType::QString:
185 d = QString();
186 break;
187 default:
188 // Unsupported. Remains undefined.
189 break;
190 }
191 }
192
193 explicit QJSPrimitiveValue(const QVariant &variant) noexcept
194 : QJSPrimitiveValue(variant.metaType(), variant.data())
195 {
196 }
197
198 constexpr QMetaType metaType() const { return d.metaType(); }
199 constexpr void *data() { return d.data(); }
200 constexpr const void *data() const { return d.data(); }
201 constexpr const void *constData() const { return d.data(); }
202
203 template<Type type>
204 QJSPrimitiveValue to() const {
205 if constexpr (type == Undefined)
206 return QJSPrimitiveUndefined();
207 if constexpr (type == Null)
208 return QJSPrimitiveNull();
209 if constexpr (type == Boolean)
210 return toBoolean();
211 if constexpr (type == Integer)
212 return toInteger();
213 if constexpr (type == Double)
214 return toDouble();
215 if constexpr (type == String)
216 return toString();
217
218 Q_UNREACHABLE_RETURN(QJSPrimitiveUndefined());
219 }
220
221 constexpr bool toBoolean() const
222 {
223 switch (type()) {
224 case Undefined: return false;
225 case Null: return false;
226 case Boolean: return asBoolean();
227 case Integer: return asInteger() != 0;
228 case Double: {
229 const double v = asDouble();
230 return !QJSNumberCoercion::equals(lhs: v, rhs: 0) && !std::isnan(x: v);
231 }
232 case String: return !asString().isEmpty();
233 default: Q_UNREACHABLE();
234 }
235
236 return false;
237 }
238
239 constexpr int toInteger() const
240 {
241 switch (type()) {
242 case Undefined: return 0;
243 case Null: return 0;
244 case Boolean: return asBoolean();
245 case Integer: return asInteger();
246 case Double: return QJSNumberCoercion::toInteger(d: asDouble());
247 case String: return fromString(string: asString()).toInteger();
248 default: Q_UNREACHABLE();
249 }
250
251 return 0;
252 }
253
254 constexpr double toDouble() const
255 {
256 switch (type()) {
257 case Undefined: return std::numeric_limits<double>::quiet_NaN();
258 case Null: return 0;
259 case Boolean: return asBoolean();
260 case Integer: return asInteger();
261 case Double: return asDouble();
262 case String: return fromString(string: asString()).toDouble();
263 default: Q_UNREACHABLE();
264 }
265
266 return {};
267 }
268
269 QString toString() const
270 {
271 switch (type()) {
272 case Undefined: return QStringLiteral("undefined");
273 case Null: return QStringLiteral("null");
274 case Boolean: return asBoolean() ? QStringLiteral("true") : QStringLiteral("false");
275 case Integer: return QString::number(asInteger());
276 case Double: {
277 const double result = asDouble();
278 if (std::isnan(x: result))
279 return QStringLiteral("NaN");
280 if (std::isfinite(x: result))
281 return toString(d: result);
282 if (result > 0)
283 return QStringLiteral("Infinity");
284 return QStringLiteral("-Infinity");
285 }
286 case String: return asString();
287 }
288
289 Q_UNREACHABLE_RETURN(QString());
290 }
291
292 QVariant toVariant() const
293 {
294 switch (type()) {
295 case Undefined: return QVariant();
296 case Null: return QVariant::fromValue<std::nullptr_t>(value: nullptr);
297 case Boolean: return QVariant(asBoolean());
298 case Integer: return QVariant(asInteger());
299 case Double: return QVariant(asDouble());
300 case String: return QVariant(asString());
301 }
302
303 Q_UNREACHABLE_RETURN(QVariant());
304 }
305
306 friend inline QJSPrimitiveValue operator+(const QJSPrimitiveValue &lhs,
307 const QJSPrimitiveValue &rhs)
308 {
309 return operate<AddOperators>(lhs, rhs);
310 }
311
312 friend inline QJSPrimitiveValue operator-(const QJSPrimitiveValue &lhs,
313 const QJSPrimitiveValue &rhs)
314 {
315 return operate<SubOperators>(lhs, rhs);
316 }
317
318 friend inline QJSPrimitiveValue operator*(const QJSPrimitiveValue &lhs,
319 const QJSPrimitiveValue &rhs)
320 {
321 return operate<MulOperators>(lhs, rhs);
322 }
323
324 friend inline QJSPrimitiveValue operator/(const QJSPrimitiveValue &lhs,
325 const QJSPrimitiveValue &rhs)
326 {
327 return operate<DivOperators>(lhs, rhs);
328 }
329
330 friend inline QJSPrimitiveValue operator%(const QJSPrimitiveValue &lhs,
331 const QJSPrimitiveValue &rhs)
332 {
333 switch (lhs.type()) {
334 case Null:
335 case Boolean:
336 case Integer:
337 switch (rhs.type()) {
338 case Boolean:
339 case Integer: {
340 const int leftInt = lhs.toInteger();
341 const int rightInt = rhs.toInteger();
342 if (leftInt >= 0 && rightInt > 0)
343 return leftInt % rightInt;
344 Q_FALLTHROUGH();
345 }
346 default:
347 break;
348 }
349 Q_FALLTHROUGH();
350 default:
351 break;
352 }
353
354 return std::fmod(x: lhs.toDouble(), y: rhs.toDouble());
355 }
356
357 QJSPrimitiveValue &operator++()
358 {
359 // ++a is modeled as a -= (-1) to avoid the potential string concatenation
360 return (*this = operate<SubOperators>(lhs: *this, rhs: -1));
361 }
362
363 QJSPrimitiveValue operator++(int)
364 {
365 // a++ is modeled as a -= (-1) to avoid the potential string concatenation
366 QJSPrimitiveValue other = operate<SubOperators>(lhs: *this, rhs: -1);
367 std::swap(a&: other, b&: *this);
368 return +other; // We still need to coerce the original value.
369 }
370
371 QJSPrimitiveValue &operator--()
372 {
373 return (*this = operate<SubOperators>(lhs: *this, rhs: 1));
374 }
375
376 QJSPrimitiveValue operator--(int)
377 {
378 QJSPrimitiveValue other = operate<SubOperators>(lhs: *this, rhs: 1);
379 std::swap(a&: other, b&: *this);
380 return +other; // We still need to coerce the original value.
381 }
382
383 QJSPrimitiveValue operator+()
384 {
385 // +a is modeled as a -= 0. That should force it to number.
386 return (*this = operate<SubOperators>(lhs: *this, rhs: 0));
387 }
388
389 QJSPrimitiveValue operator-()
390 {
391 return (*this = operate<MulOperators>(lhs: *this, rhs: -1));
392 }
393
394 constexpr bool strictlyEquals(const QJSPrimitiveValue &other) const
395 {
396 const Type myType = type();
397 const Type otherType = other.type();
398
399 if (myType != otherType) {
400 // int -> double promotion is OK in strict mode
401 if (myType == Double && otherType == Integer)
402 return strictlyEquals(other: double(other.asInteger()));
403 if (myType == Integer && otherType == Double)
404 return QJSPrimitiveValue(double(asInteger())).strictlyEquals(other);
405 return false;
406 }
407
408 switch (myType) {
409 case Undefined:
410 case Null:
411 return true;
412 case Boolean:
413 return asBoolean() == other.asBoolean();
414 case Integer:
415 return asInteger() == other.asInteger();
416 case Double: {
417 const double l = asDouble();
418 const double r = other.asDouble();
419 if (std::isnan(x: l) || std::isnan(x: r))
420 return false;
421 if (qIsNull(d: l) && qIsNull(d: r))
422 return true;
423 return QJSNumberCoercion::equals(lhs: l, rhs: r);
424 }
425 case String:
426 return asString() == other.asString();
427 }
428
429 return false;
430 }
431
432 // Loose operator==, in contrast to strict ===
433 constexpr bool equals(const QJSPrimitiveValue &other) const
434 {
435 const Type myType = type();
436 const Type otherType = other.type();
437
438 if (myType == otherType)
439 return strictlyEquals(other);
440
441 switch (myType) {
442 case Undefined:
443 return otherType == Null;
444 case Null:
445 return otherType == Undefined;
446 case Boolean:
447 return QJSPrimitiveValue(int(asBoolean())).equals(other);
448 case Integer:
449 // prefer rhs bool -> int promotion over promoting both to double
450 return otherType == Boolean
451 ? QJSPrimitiveValue(asInteger()).equals(other: int(other.asBoolean()))
452 : QJSPrimitiveValue(double(asInteger())).equals(other);
453 case Double:
454 // Promote the other side to double (or recognize lhs as undefined/null)
455 return other.equals(other: *this);
456 case String:
457 return fromString(string: asString()).parsedEquals(other);
458 }
459
460 return false;
461 }
462
463 friend constexpr inline bool operator==(const QJSPrimitiveValue &lhs, const
464 QJSPrimitiveValue &rhs)
465 {
466 return lhs.strictlyEquals(other: rhs);
467 }
468
469 friend constexpr inline bool operator!=(const QJSPrimitiveValue &lhs,
470 const QJSPrimitiveValue &rhs)
471 {
472 return !lhs.strictlyEquals(other: rhs);
473 }
474
475 friend constexpr inline bool operator<(const QJSPrimitiveValue &lhs,
476 const QJSPrimitiveValue &rhs)
477 {
478 switch (lhs.type()) {
479 case Undefined:
480 return false;
481 case Null: {
482 switch (rhs.type()) {
483 case Undefined: return false;
484 case Null: return false;
485 case Boolean: return 0 < int(rhs.asBoolean());
486 case Integer: return 0 < rhs.asInteger();
487 case Double: return double(0) < rhs.asDouble();
488 case String: return double(0) < rhs.toDouble();
489 }
490 break;
491 }
492 case Boolean: {
493 switch (rhs.type()) {
494 case Undefined: return false;
495 case Null: return int(lhs.asBoolean()) < 0;
496 case Boolean: return lhs.asBoolean() < rhs.asBoolean();
497 case Integer: return int(lhs.asBoolean()) < rhs.asInteger();
498 case Double: return double(lhs.asBoolean()) < rhs.asDouble();
499 case String: return double(lhs.asBoolean()) < rhs.toDouble();
500 }
501 break;
502 }
503 case Integer: {
504 switch (rhs.type()) {
505 case Undefined: return false;
506 case Null: return lhs.asInteger() < 0;
507 case Boolean: return lhs.asInteger() < int(rhs.asBoolean());
508 case Integer: return lhs.asInteger() < rhs.asInteger();
509 case Double: return double(lhs.asInteger()) < rhs.asDouble();
510 case String: return double(lhs.asInteger()) < rhs.toDouble();
511 }
512 break;
513 }
514 case Double: {
515 switch (rhs.type()) {
516 case Undefined: return false;
517 case Null: return lhs.asDouble() < double(0);
518 case Boolean: return lhs.asDouble() < double(rhs.asBoolean());
519 case Integer: return lhs.asDouble() < double(rhs.asInteger());
520 case Double: return lhs.asDouble() < rhs.asDouble();
521 case String: return lhs.asDouble() < rhs.toDouble();
522 }
523 break;
524 }
525 case String: {
526 switch (rhs.type()) {
527 case Undefined: return false;
528 case Null: return lhs.toDouble() < double(0);
529 case Boolean: return lhs.toDouble() < double(rhs.asBoolean());
530 case Integer: return lhs.toDouble() < double(rhs.asInteger());
531 case Double: return lhs.toDouble() < rhs.asDouble();
532 case String: return lhs.asString() < rhs.asString();
533 }
534 break;
535 }
536 }
537
538 return false;
539 }
540
541 friend constexpr inline bool operator>(const QJSPrimitiveValue &lhs, const QJSPrimitiveValue &rhs)
542 {
543 return rhs < lhs;
544 }
545
546 friend constexpr inline bool operator<=(const QJSPrimitiveValue &lhs, const QJSPrimitiveValue &rhs)
547 {
548 if (lhs.type() == String) {
549 if (rhs.type() == String)
550 return lhs.asString() <= rhs.asString();
551 else
552 return fromString(string: lhs.asString()) <= rhs;
553 }
554 if (rhs.type() == String)
555 return lhs <= fromString(string: rhs.asString());
556
557 if (lhs.isNanOrUndefined() || rhs.isNanOrUndefined())
558 return false;
559 return !(lhs > rhs);
560 }
561
562 friend constexpr inline bool operator>=(const QJSPrimitiveValue &lhs, const QJSPrimitiveValue &rhs)
563 {
564 if (lhs.type() == String) {
565 if (rhs.type() == String)
566 return lhs.asString() >= rhs.asString();
567 else
568 return fromString(string: lhs.asString()) >= rhs;
569 }
570 if (rhs.type() == String)
571 return lhs >= fromString(string: rhs.asString());
572
573 if (lhs.isNanOrUndefined() || rhs.isNanOrUndefined())
574 return false;
575 return !(lhs < rhs);
576 }
577
578private:
579 friend class QJSManagedValue;
580 friend class QJSValue;
581 friend struct QV4::ExecutionEngine;
582
583 constexpr bool asBoolean() const { return d.getBool(); }
584 constexpr int asInteger() const { return d.getInt(); }
585 constexpr double asDouble() const { return d.getDouble(); }
586 QString asString() const { return d.getString(); }
587
588 constexpr bool parsedEquals(const QJSPrimitiveValue &other) const
589 {
590 return type() != Undefined && equals(other);
591 }
592
593 static QJSPrimitiveValue fromString(const QString &string)
594 {
595 bool ok;
596 const int intValue = string.toInt(ok: &ok);
597 if (ok)
598 return intValue;
599
600 const double doubleValue = string.toDouble(ok: &ok);
601 if (ok)
602 return doubleValue;
603 if (string == QStringLiteral("Infinity"))
604 return std::numeric_limits<double>::infinity();
605 if (string == QStringLiteral("-Infinity"))
606 return -std::numeric_limits<double>::infinity();
607 if (string == QStringLiteral("NaN"))
608 return std::numeric_limits<double>::quiet_NaN();
609 return QJSPrimitiveUndefined();
610 }
611
612 static Q_QML_EXPORT QString toString(double d);
613
614 template<typename Operators, typename Lhs, typename Rhs>
615 static QJSPrimitiveValue operateOnIntegers(const QJSPrimitiveValue &lhs,
616 const QJSPrimitiveValue &rhs)
617 {
618 int result;
619 if (Operators::opOverflow(lhs.d.get<Lhs>(), rhs.d.get<Rhs>(), &result))
620 return Operators::op(lhs.d.get<Lhs>(), rhs.d.get<Rhs>());
621 return result;
622 }
623
624 template<typename Operators>
625 static QJSPrimitiveValue operate(const QJSPrimitiveValue &lhs, const QJSPrimitiveValue &rhs)
626 {
627 switch (lhs.type()) {
628 case Undefined:
629 switch (rhs.type()) {
630 case Undefined: return std::numeric_limits<double>::quiet_NaN();
631 case Null: return std::numeric_limits<double>::quiet_NaN();
632 case Boolean: return std::numeric_limits<double>::quiet_NaN();
633 case Integer: return std::numeric_limits<double>::quiet_NaN();
634 case Double: return std::numeric_limits<double>::quiet_NaN();
635 case String: return Operators::op(QJSPrimitiveUndefined(), rhs.asString());
636 }
637 break;
638 case Null:
639 switch (rhs.type()) {
640 case Undefined: return std::numeric_limits<double>::quiet_NaN();
641 case Null: return operateOnIntegers<Operators, int, int>(0, 0);
642 case Boolean: return operateOnIntegers<Operators, int, bool>(0, rhs);
643 case Integer: return operateOnIntegers<Operators, int, int>(0, rhs);
644 case Double: return Operators::op(0, rhs.asDouble());
645 case String: return Operators::op(QJSPrimitiveNull(), rhs.asString());
646 }
647 break;
648 case Boolean:
649 switch (rhs.type()) {
650 case Undefined: return std::numeric_limits<double>::quiet_NaN();
651 case Null: return operateOnIntegers<Operators, bool, int>(lhs, 0);
652 case Boolean: return operateOnIntegers<Operators, bool, bool>(lhs, rhs);
653 case Integer: return operateOnIntegers<Operators, bool, int>(lhs, rhs);
654 case Double: return Operators::op(lhs.asBoolean(), rhs.asDouble());
655 case String: return Operators::op(lhs.asBoolean(), rhs.asString());
656 }
657 break;
658 case Integer:
659 switch (rhs.type()) {
660 case Undefined: return std::numeric_limits<double>::quiet_NaN();
661 case Null: return operateOnIntegers<Operators, int, int>(lhs, 0);
662 case Boolean: return operateOnIntegers<Operators, int, bool>(lhs, rhs);
663 case Integer: return operateOnIntegers<Operators, int, int>(lhs, rhs);
664 case Double: return Operators::op(lhs.asInteger(), rhs.asDouble());
665 case String: return Operators::op(lhs.asInteger(), rhs.asString());
666 }
667 break;
668 case Double:
669 switch (rhs.type()) {
670 case Undefined: return std::numeric_limits<double>::quiet_NaN();
671 case Null: return Operators::op(lhs.asDouble(), 0);
672 case Boolean: return Operators::op(lhs.asDouble(), rhs.asBoolean());
673 case Integer: return Operators::op(lhs.asDouble(), rhs.asInteger());
674 case Double: return Operators::op(lhs.asDouble(), rhs.asDouble());
675 case String: return Operators::op(lhs.asDouble(), rhs.asString());
676 }
677 break;
678 case String:
679 switch (rhs.type()) {
680 case Undefined: return Operators::op(lhs.asString(), QJSPrimitiveUndefined());
681 case Null: return Operators::op(lhs.asString(), QJSPrimitiveNull());
682 case Boolean: return Operators::op(lhs.asString(), rhs.asBoolean());
683 case Integer: return Operators::op(lhs.asString(), rhs.asInteger());
684 case Double: return Operators::op(lhs.asString(), rhs.asDouble());
685 case String: return Operators::op(lhs.asString(), rhs.asString());
686 }
687 break;
688 }
689
690 Q_UNREACHABLE_RETURN(QJSPrimitiveUndefined());
691 }
692
693 constexpr bool isNanOrUndefined() const
694 {
695 switch (type()) {
696 case Undefined: return true;
697 case Double: return std::isnan(x: asDouble());
698 default: return false;
699 }
700 }
701
702 struct QJSPrimitiveValuePrivate
703 {
704 // Can't be default because QString has a non-trivial ctor.
705 constexpr QJSPrimitiveValuePrivate() noexcept {}
706
707 Q_IMPLICIT constexpr QJSPrimitiveValuePrivate(QJSPrimitiveUndefined) noexcept {}
708 Q_IMPLICIT constexpr QJSPrimitiveValuePrivate(QJSPrimitiveNull) noexcept
709 : m_type(Null) {}
710 Q_IMPLICIT constexpr QJSPrimitiveValuePrivate(bool b) noexcept
711 : m_bool(b), m_type(Boolean) {}
712 Q_IMPLICIT constexpr QJSPrimitiveValuePrivate(int i) noexcept
713 : m_int(i), m_type(Integer) {}
714 Q_IMPLICIT constexpr QJSPrimitiveValuePrivate(double d) noexcept
715 : m_double(d), m_type(Double) {}
716 Q_IMPLICIT QJSPrimitiveValuePrivate(QString s) noexcept
717 : m_string(std::move(s)), m_type(String) {}
718
719 constexpr QJSPrimitiveValuePrivate(const QJSPrimitiveValuePrivate &other) noexcept
720 : m_type(other.m_type)
721 {
722 // Not copy-and-swap since swap() would be much more complicated.
723 if (!assignSimple(other))
724 new (&m_string) QString(other.m_string);
725 }
726
727 constexpr QJSPrimitiveValuePrivate(QJSPrimitiveValuePrivate &&other) noexcept
728 : m_type(other.m_type)
729 {
730 // Not move-and-swap since swap() would be much more complicated.
731 if (!assignSimple(other))
732 new (&m_string) QString(std::move(other.m_string));
733 }
734
735 constexpr QJSPrimitiveValuePrivate &operator=(const QJSPrimitiveValuePrivate &other) noexcept
736 {
737 if (this == &other)
738 return *this;
739
740 if (m_type == String) {
741 if (other.m_type == String) {
742 m_type = other.m_type;
743 m_string = other.m_string;
744 return *this;
745 }
746 m_string.~QString();
747 }
748
749 m_type = other.m_type;
750 if (!assignSimple(other))
751 new (&m_string) QString(other.m_string);
752 return *this;
753 }
754
755 constexpr QJSPrimitiveValuePrivate &operator=(QJSPrimitiveValuePrivate &&other) noexcept
756 {
757 if (this == &other)
758 return *this;
759
760 if (m_type == String) {
761 if (other.m_type == String) {
762 m_type = other.m_type;
763 m_string = std::move(other.m_string);
764 return *this;
765 }
766 m_string.~QString();
767 }
768
769 m_type = other.m_type;
770 if (!assignSimple(other))
771 new (&m_string) QString(std::move(other.m_string));
772 return *this;
773 }
774
775 ~QJSPrimitiveValuePrivate()
776 {
777 if (m_type == String)
778 m_string.~QString();
779 }
780
781 constexpr Type type() const noexcept { return m_type; }
782 constexpr bool getBool() const noexcept { return m_bool; }
783 constexpr int getInt() const noexcept { return m_int; }
784 constexpr double getDouble() const noexcept { return m_double; }
785 QString getString() const noexcept { return m_string; }
786
787 template<typename T>
788 constexpr T get() const noexcept {
789 if constexpr (std::is_same_v<T, QJSPrimitiveUndefined>)
790 return QJSPrimitiveUndefined();
791 else if constexpr (std::is_same_v<T, QJSPrimitiveNull>)
792 return QJSPrimitiveNull();
793 else if constexpr (std::is_same_v<T, bool>)
794 return getBool();
795 else if constexpr (std::is_same_v<T, int>)
796 return getInt();
797 else if constexpr (std::is_same_v<T, double>)
798 return getDouble();
799 else if constexpr (std::is_same_v<T, QString>)
800 return getString();
801
802 // GCC 8.x does not treat __builtin_unreachable() as constexpr
803 #if !defined(Q_CC_GNU_ONLY) || (Q_CC_GNU >= 900)
804 // NOLINTNEXTLINE(qt-use-unreachable-return): Triggers on Clang, breaking GCC 8
805 Q_UNREACHABLE();
806 #endif
807 return T();
808 }
809
810 constexpr QMetaType metaType() const noexcept {
811 switch (m_type) {
812 case Undefined:
813 return QMetaType();
814 case Null:
815 return QMetaType::fromType<std::nullptr_t>();
816 case Boolean:
817 return QMetaType::fromType<bool>();
818 case Integer:
819 return QMetaType::fromType<int>();
820 case Double:
821 return QMetaType::fromType<double>();
822 case String:
823 return QMetaType::fromType<QString>();
824 }
825
826 // GCC 8.x does not treat __builtin_unreachable() as constexpr
827 #if !defined(Q_CC_GNU_ONLY) || (Q_CC_GNU >= 900)
828 // NOLINTNEXTLINE(qt-use-unreachable-return): Triggers on Clang, breaking GCC 8
829 Q_UNREACHABLE();
830 #endif
831 return QMetaType();
832 }
833
834 constexpr void *data() noexcept {
835 switch (m_type) {
836 case Undefined:
837 case Null:
838 return nullptr;
839 case Boolean:
840 return &m_bool;
841 case Integer:
842 return &m_int;
843 case Double:
844 return &m_double;
845 case String:
846 return &m_string;
847 }
848
849 // GCC 8.x does not treat __builtin_unreachable() as constexpr
850 #if !defined(Q_CC_GNU_ONLY) || (Q_CC_GNU >= 900)
851 // NOLINTNEXTLINE(qt-use-unreachable-return): Triggers on Clang, breaking GCC 8
852 Q_UNREACHABLE();
853 #endif
854 return nullptr;
855 }
856
857 constexpr const void *data() const noexcept {
858 switch (m_type) {
859 case Undefined:
860 case Null:
861 return nullptr;
862 case Boolean:
863 return &m_bool;
864 case Integer:
865 return &m_int;
866 case Double:
867 return &m_double;
868 case String:
869 return &m_string;
870 }
871
872 // GCC 8.x does not treat __builtin_unreachable() as constexpr
873 #if !defined(Q_CC_GNU_ONLY) || (Q_CC_GNU >= 900)
874 // NOLINTNEXTLINE(qt-use-unreachable-return): Triggers on Clang, breaking GCC 8
875 Q_UNREACHABLE();
876 #endif
877 return nullptr;
878 }
879
880 private:
881 constexpr bool assignSimple(const QJSPrimitiveValuePrivate &other) noexcept
882 {
883 switch (other.m_type) {
884 case Undefined:
885 case Null:
886 return true;
887 case Boolean:
888 m_bool = other.m_bool;
889 return true;
890 case Integer:
891 m_int = other.m_int;
892 return true;
893 case Double:
894 m_double = other.m_double;
895 return true;
896 case String:
897 return false;
898 default:
899 Q_UNREACHABLE();
900 }
901 return false;
902 }
903
904 union {
905 bool m_bool = false;
906 int m_int;
907 double m_double;
908 QString m_string;
909 };
910
911 Type m_type = Undefined;
912 };
913
914 QJSPrimitiveValuePrivate d;
915};
916
917namespace QQmlPrivate {
918 // TODO: Make this constexpr once std::isnan is constexpr.
919 inline double jsExponentiate(double base, double exponent)
920 {
921 constexpr double qNaN = std::numeric_limits<double>::quiet_NaN();
922 constexpr double inf = std::numeric_limits<double>::infinity();
923
924 if (qIsNull(d: exponent))
925 return 1.0;
926
927 if (std::isnan(x: exponent))
928 return qNaN;
929
930 if (QJSNumberCoercion::equals(lhs: base, rhs: 1.0) || QJSNumberCoercion::equals(lhs: base, rhs: -1.0))
931 return std::isinf(x: exponent) ? qNaN : std::pow(x: base, y: exponent);
932
933 if (!qIsNull(d: base))
934 return std::pow(x: base, y: exponent);
935
936 if (std::copysign(x: 1.0, y: base) > 0.0)
937 return exponent < 0.0 ? inf : std::pow(x: base, y: exponent);
938
939 if (exponent < 0.0)
940 return QJSNumberCoercion::equals(lhs: std::fmod(x: -exponent, y: 2.0), rhs: 1.0) ? -inf : inf;
941
942 return QJSNumberCoercion::equals(lhs: std::fmod(x: exponent, y: 2.0), rhs: 1.0)
943 ? std::copysign(x: 0, y: -1.0)
944 : 0.0;
945 }
946}
947
948QT_END_NAMESPACE
949
950#endif // QJSPRIMITIVEVALUE_H
951

source code of qtdeclarative/src/qml/jsapi/qjsprimitivevalue.h