1/*
2 * Copyright (C) 2021 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "TemporalCalendarPrototype.h"
28
29#include "BuiltinNames.h"
30#include "IteratorOperations.h"
31#include "JSCInlines.h"
32#include "ObjectConstructor.h"
33
34namespace JSC {
35
36static JSC_DECLARE_HOST_FUNCTION(temporalCalendarPrototypeFuncFields);
37static JSC_DECLARE_HOST_FUNCTION(temporalCalendarPrototypeFuncMergeFields);
38static JSC_DECLARE_HOST_FUNCTION(temporalCalendarPrototypeFuncToString);
39static JSC_DECLARE_HOST_FUNCTION(temporalCalendarPrototypeFuncToJSON);
40static JSC_DECLARE_CUSTOM_GETTER(temporalCalendarPrototypeGetterId);
41
42}
43
44#include "TemporalCalendarPrototype.lut.h"
45
46namespace JSC {
47
48const ClassInfo TemporalCalendarPrototype::s_info = { "Temporal.Calendar", &Base::s_info, &temporalCalendarPrototypeTable, nullptr, CREATE_METHOD_TABLE(TemporalCalendarPrototype) };
49
50/* Source for TemporalCalendarPrototype.lut.h
51@begin temporalCalendarPrototypeTable
52 fields temporalCalendarPrototypeFuncFields DontEnum|Function 1
53 mergeFields temporalCalendarPrototypeFuncMergeFields DontEnum|Function 2
54 toString temporalCalendarPrototypeFuncToString DontEnum|Function 0
55 toJSON temporalCalendarPrototypeFuncToJSON DontEnum|Function 0
56 id temporalCalendarPrototypeGetterId ReadOnly|DontEnum|CustomAccessor
57@end
58*/
59
60TemporalCalendarPrototype* TemporalCalendarPrototype::create(VM& vm, JSGlobalObject* globalObject, Structure* structure)
61{
62 TemporalCalendarPrototype* object = new (NotNull, allocateCell<TemporalCalendarPrototype>(vm.heap)) TemporalCalendarPrototype(vm, structure);
63 object->finishCreation(vm, globalObject);
64 return object;
65}
66
67Structure* TemporalCalendarPrototype::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype)
68{
69 return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info());
70}
71
72TemporalCalendarPrototype::TemporalCalendarPrototype(VM& vm, Structure* structure)
73 : Base(vm, structure)
74{
75}
76
77void TemporalCalendarPrototype::finishCreation(VM& vm, JSGlobalObject*)
78{
79 Base::finishCreation(vm);
80 ASSERT(inherits(vm, info()));
81 JSC_TO_STRING_TAG_WITHOUT_TRANSITION();
82}
83
84// https://tc39.es/proposal-temporal/#sec-get-temporal.calendar.prototype.id
85JSC_DEFINE_CUSTOM_GETTER(temporalCalendarPrototypeGetterId, (JSGlobalObject* globalObject, EncodedJSValue thisValue, PropertyName))
86{
87 return JSValue::encode(JSValue::decode(thisValue).toString(globalObject));
88}
89
90// https://tc39.es/proposal-temporal/#sup-temporal.calendar.prototype.fields
91JSC_DEFINE_HOST_FUNCTION(temporalCalendarPrototypeFuncFields, (JSGlobalObject* globalObject, CallFrame* callFrame))
92{
93 VM& vm = globalObject->vm();
94 auto scope = DECLARE_THROW_SCOPE(vm);
95
96 auto* calendar = jsDynamicCast<TemporalCalendar*>(vm, callFrame->thisValue());
97 if (!calendar)
98 return throwVMTypeError(globalObject, scope, "Temporal.Calendar.prototype.fields called on value that's not an object initialized as a Calendar"_s);
99
100 bool isISO8601 = calendar->isISO8601();
101 bool shouldAddEraAndEraYear = false;
102 MarkedArgumentBuffer fieldNames;
103 forEachInIterable(globalObject, callFrame->argument(0), [isISO8601, &shouldAddEraAndEraYear, &fieldNames](VM& vm, JSGlobalObject* globalObject, JSValue value) {
104 auto scope = DECLARE_THROW_SCOPE(vm);
105 if (!value.isString()) {
106 throwTypeError(globalObject, scope, "fields include non string value"_s);
107 return;
108 }
109 if (!isISO8601 && !shouldAddEraAndEraYear) {
110 auto string = jsCast<JSString*>(value)->value(globalObject);
111 RETURN_IF_EXCEPTION(scope, void());
112 if (string == "year"_s)
113 shouldAddEraAndEraYear = true;
114 }
115 fieldNames.append(value);
116 });
117 RETURN_IF_EXCEPTION(scope, { });
118
119 if (shouldAddEraAndEraYear) {
120 fieldNames.append(jsNontrivialString(vm, vm.propertyNames->era.impl()));
121 fieldNames.append(jsNontrivialString(vm, vm.propertyNames->eraYear.impl()));
122 }
123
124 return JSValue::encode(constructArray(globalObject, static_cast<ArrayAllocationProfile*>(nullptr), fieldNames));
125}
126
127// https://tc39.es/proposal-temporal/#sec-temporal-defaultmergefields
128static JSObject* defaultMergeFields(JSGlobalObject* globalObject, JSObject* fields, JSObject* additionalFields)
129{
130 VM& vm = globalObject->vm();
131 auto scope = DECLARE_THROW_SCOPE(vm);
132
133 auto* merged = constructEmptyObject(globalObject);
134
135 {
136 PropertyNameArray originalKeys(vm, PropertyNameMode::Strings, PrivateSymbolMode::Exclude);
137 fields->methodTable(vm)->getOwnPropertyNames(fields, globalObject, originalKeys, DontEnumPropertiesMode::Include);
138 RETURN_IF_EXCEPTION(scope, { });
139
140 for (const auto& nextKey : originalKeys) {
141 if (nextKey != vm.propertyNames->month && nextKey != vm.propertyNames->monthCode) {
142 JSValue propValue = fields->get(globalObject, nextKey);
143 RETURN_IF_EXCEPTION(scope, { });
144 if (!propValue.isUndefined()) {
145 PutPropertySlot slot(merged, true);
146 merged->putOwnDataProperty(vm, nextKey, propValue, slot);
147 RETURN_IF_EXCEPTION(scope, { });
148 }
149 }
150 }
151 }
152
153 bool includesMonthOrMonthCode = false;
154 {
155 PropertyNameArray newKeys(vm, PropertyNameMode::Strings, PrivateSymbolMode::Exclude);
156 additionalFields->methodTable(vm)->getOwnPropertyNames(additionalFields, globalObject, newKeys, DontEnumPropertiesMode::Include);
157 RETURN_IF_EXCEPTION(scope, { });
158
159 for (const auto& nextKey : newKeys) {
160 if (!includesMonthOrMonthCode) {
161 if (nextKey == vm.propertyNames->month || nextKey == vm.propertyNames->monthCode)
162 includesMonthOrMonthCode = true;
163 }
164
165 JSValue propValue = additionalFields->get(globalObject, nextKey);
166 RETURN_IF_EXCEPTION(scope, { });
167 if (!propValue.isUndefined()) {
168 PutPropertySlot slot(merged, true);
169 merged->putOwnDataProperty(vm, nextKey, propValue, slot);
170 RETURN_IF_EXCEPTION(scope, { });
171 }
172 }
173 }
174
175 if (!includesMonthOrMonthCode) {
176 JSValue month = fields->get(globalObject, vm.propertyNames->month);
177 RETURN_IF_EXCEPTION(scope, { });
178 if (!month.isUndefined()) {
179 PutPropertySlot slot(merged, true);
180 merged->putOwnDataProperty(vm, vm.propertyNames->month, month, slot);
181 RETURN_IF_EXCEPTION(scope, { });
182 }
183
184 JSValue monthCode = fields->get(globalObject, vm.propertyNames->monthCode);
185 RETURN_IF_EXCEPTION(scope, { });
186 if (!monthCode.isUndefined()) {
187 PutPropertySlot slot(merged, true);
188 merged->putOwnDataProperty(vm, vm.propertyNames->monthCode, monthCode, slot);
189 RETURN_IF_EXCEPTION(scope, { });
190 }
191 }
192
193 return merged;
194}
195
196// https://tc39.es/proposal-temporal/#sup-temporal.calendar.prototype.mergefields
197JSC_DEFINE_HOST_FUNCTION(temporalCalendarPrototypeFuncMergeFields, (JSGlobalObject* globalObject, CallFrame* callFrame))
198{
199 VM& vm = globalObject->vm();
200 auto scope = DECLARE_THROW_SCOPE(vm);
201
202 auto* calendar = jsDynamicCast<TemporalCalendar*>(vm, callFrame->thisValue());
203 if (!calendar)
204 return throwVMTypeError(globalObject, scope, "Temporal.Calendar.prototype.mergeFields called on value that's not an object initialized as a Calendar"_s);
205
206 auto* fields = callFrame->argument(0).toObject(globalObject);
207 RETURN_IF_EXCEPTION(scope, { });
208
209 auto* additionalFields = callFrame->argument(1).toObject(globalObject);
210 RETURN_IF_EXCEPTION(scope, { });
211
212 if (calendar->isISO8601())
213 RELEASE_AND_RETURN(scope, JSValue::encode(defaultMergeFields(globalObject, fields, additionalFields)));
214
215 auto copyObject = [](JSGlobalObject* globalObject, JSObject* object) -> JSObject* {
216 VM& vm = globalObject->vm();
217 auto scope = DECLARE_THROW_SCOPE(vm);
218
219 auto* copied = constructEmptyObject(globalObject);
220
221 PropertyNameArray keys(vm, PropertyNameMode::Strings, PrivateSymbolMode::Exclude);
222 object->methodTable(vm)->getOwnPropertyNames(object, globalObject, keys, DontEnumPropertiesMode::Include);
223 RETURN_IF_EXCEPTION(scope, { });
224
225 for (const auto& key : keys) {
226 JSValue propValue = object->get(globalObject, key);
227 RETURN_IF_EXCEPTION(scope, { });
228 if (!propValue.isUndefined()) {
229 PutPropertySlot slot(copied, true);
230 copied->putOwnDataProperty(vm, key, propValue, slot);
231 RETURN_IF_EXCEPTION(scope, { });
232 }
233 }
234
235 return copied;
236 };
237
238 auto* fieldsCopied = copyObject(globalObject, fields);
239 RETURN_IF_EXCEPTION(scope, { });
240
241 auto* additionalFieldsCopied = copyObject(globalObject, additionalFields);
242 RETURN_IF_EXCEPTION(scope, { });
243
244 JSValue newMonth = jsUndefined();
245 JSValue newMonthCode = jsUndefined();
246 JSValue newYear = jsUndefined();
247 JSValue newEra = jsUndefined();
248 JSValue newEraYear = jsUndefined();
249 {
250 PropertyNameArray keys(vm, PropertyNameMode::Strings, PrivateSymbolMode::Exclude);
251 additionalFieldsCopied->methodTable(vm)->getOwnPropertyNames(additionalFieldsCopied, globalObject, keys, DontEnumPropertiesMode::Include);
252 RETURN_IF_EXCEPTION(scope, { });
253
254 for (const auto& key : keys) {
255 JSValue propValue = additionalFieldsCopied->get(globalObject, key);
256 RETURN_IF_EXCEPTION(scope, { });
257 if (!propValue.isUndefined()) {
258 if (key == vm.propertyNames->month)
259 newMonth = propValue;
260 else if (key == vm.propertyNames->monthCode)
261 newMonthCode = propValue;
262 else if (key == vm.propertyNames->year)
263 newYear = propValue;
264 else if (key == vm.propertyNames->era)
265 newEra = propValue;
266 else if (key == vm.propertyNames->eraYear)
267 newEraYear = propValue;
268 else {
269 PutPropertySlot slot(fieldsCopied, true);
270 fieldsCopied->putOwnDataProperty(vm, key, propValue, slot);
271 RETURN_IF_EXCEPTION(scope, { });
272 }
273 }
274 }
275 }
276
277 if (!newMonth.isUndefined() || !newMonthCode.isUndefined()) {
278 {
279 PutPropertySlot slot(fieldsCopied, true);
280 fieldsCopied->putOwnDataProperty(vm, vm.propertyNames->month, newMonth, slot);
281 RETURN_IF_EXCEPTION(scope, { });
282 }
283 {
284 PutPropertySlot slot(fieldsCopied, true);
285 fieldsCopied->putOwnDataProperty(vm, vm.propertyNames->monthCode, newMonthCode, slot);
286 RETURN_IF_EXCEPTION(scope, { });
287 }
288 }
289
290 if (!newYear.isUndefined() || !newEra.isUndefined() || !newEraYear.isUndefined()) {
291 {
292 PutPropertySlot slot(fieldsCopied, true);
293 fieldsCopied->putOwnDataProperty(vm, vm.propertyNames->year, newYear, slot);
294 RETURN_IF_EXCEPTION(scope, { });
295 }
296 {
297 PutPropertySlot slot(fieldsCopied, true);
298 fieldsCopied->putOwnDataProperty(vm, vm.propertyNames->era, newEra, slot);
299 RETURN_IF_EXCEPTION(scope, { });
300 }
301 {
302 PutPropertySlot slot(fieldsCopied, true);
303 fieldsCopied->putOwnDataProperty(vm, vm.propertyNames->eraYear, newEraYear, slot);
304 RETURN_IF_EXCEPTION(scope, { });
305 }
306 }
307
308 return JSValue::encode(fieldsCopied);
309}
310
311// https://tc39.es/proposal-temporal/#sec-temporal.calendar.prototype.tostring
312JSC_DEFINE_HOST_FUNCTION(temporalCalendarPrototypeFuncToString, (JSGlobalObject* globalObject, CallFrame* callFrame))
313{
314 VM& vm = globalObject->vm();
315 auto scope = DECLARE_THROW_SCOPE(vm);
316
317 auto* calendar = jsDynamicCast<TemporalCalendar*>(vm, callFrame->thisValue());
318 if (!calendar)
319 return throwVMTypeError(globalObject, scope, "Temporal.Calendar.prototype.toString called on value that's not an object initialized as a Calendar"_s);
320
321 return JSValue::encode(jsString(vm, intlAvailableCalendars()[calendar->identifier()]));
322}
323
324// https://tc39.es/proposal-temporal/#sec-temporal.calendar.prototype.tojson
325JSC_DEFINE_HOST_FUNCTION(temporalCalendarPrototypeFuncToJSON, (JSGlobalObject* globalObject, CallFrame* callFrame))
326{
327 return JSValue::encode(callFrame->thisValue().toString(globalObject));
328}
329
330} // namespace JSC