Skip to content

Commit

Permalink
Copy objects received from user code before passing them into user co…
Browse files Browse the repository at this point in the history
…de again

The intention of this is that any getters installed on an object received
from user code are called once each, in a defined order.

Requires a few fixes to make the polyfill match the spec text.

See: #1426
  • Loading branch information
ptomato committed Apr 9, 2021
1 parent 8a6bbfb commit 863692f
Show file tree
Hide file tree
Showing 19 changed files with 343 additions and 19 deletions.
6 changes: 3 additions & 3 deletions polyfill/lib/ecmascript.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1123,10 +1123,10 @@ export const ES = ObjectAssign({}, ES2020, {
['month', undefined],
['monthCode', undefined],
['nanosecond', 0],
['offset', undefined],
['second', 0],
['timeZone'],
['year', undefined]
['year', undefined],
['offset', undefined],
['timeZone']
];
// Add extra fields from the calendar at the end
fieldNames.forEach((fieldName) => {
Expand Down
1 change: 1 addition & 0 deletions polyfill/lib/plaindate.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ export class PlainDate {
}
let fields = ES.ToTemporalDateFields(this, fieldNames);
fields = ES.CalendarMergeFields(calendar, fields, props);
fields = ES.ToTemporalDateFields(fields, fieldNames);

options = ES.NormalizeOptionsObject(options);

Expand Down
1 change: 1 addition & 0 deletions polyfill/lib/plaindatetime.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ export class PlainDateTime {
}
let fields = ES.ToTemporalDateTimeFields(this, fieldNames);
fields = ES.CalendarMergeFields(calendar, fields, props);
fields = ES.ToTemporalDateTimeFields(fields, fieldNames);
const {
year,
month,
Expand Down
25 changes: 17 additions & 8 deletions polyfill/lib/plainmonthday.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import { DateTimeFormat } from './intl.mjs';
import { MakeIntrinsicClass } from './intrinsicclass.mjs';
import { ISO_MONTH, ISO_DAY, ISO_YEAR, CALENDAR, MONTH_DAY_BRAND, CreateSlots, GetSlot, SetSlot } from './slots.mjs';

const ObjectAssign = Object.assign;

function MonthDayToString(monthDay, showCalendar = 'auto') {
const month = ES.ISODateTimePartString(GetSlot(monthDay, ISO_MONTH));
const day = ES.ISODateTimePartString(GetSlot(monthDay, ISO_DAY));
Expand Down Expand Up @@ -88,6 +86,7 @@ export class PlainMonthDay {
}
let fields = ES.ToTemporalMonthDayFields(this, fieldNames);
fields = ES.CalendarMergeFields(calendar, fields, props);
fields = ES.ToTemporalMonthDayFields(fields, fieldNames);

options = ES.NormalizeOptionsObject(options);
return ES.MonthDayFromFields(calendar, fields, options);
Expand Down Expand Up @@ -124,18 +123,28 @@ export class PlainMonthDay {
const calendar = GetSlot(this, CALENDAR);

const receiverFieldNames = ES.CalendarFields(calendar, ['day', 'monthCode']);
const fields = ES.ToTemporalMonthDayFields(this, receiverFieldNames);
let fields = ES.ToTemporalMonthDayFields(this, receiverFieldNames);

const inputFieldNames = ES.CalendarFields(calendar, ['year']);
const entries = [['year']];
const inputEntries = [['year']];
// Add extra fields from the calendar at the end
inputFieldNames.forEach((fieldName) => {
if (!entries.some(([name]) => name === fieldName)) {
entries.push([fieldName, undefined]);
if (!inputEntries.some(([name]) => name === fieldName)) {
inputEntries.push([fieldName, undefined]);
}
});
const inputFields = ES.PrepareTemporalFields(item, inputEntries);
let mergedFields = ES.CalendarMergeFields(calendar, fields, inputFields);

const mergedFieldNames = [...new Set([...receiverFieldNames, ...inputFieldNames])];
const mergedEntries = [];
mergedFieldNames.forEach((fieldName) => {
if (!mergedEntries.some(([name]) => name === fieldName)) {
mergedEntries.push([fieldName, undefined]);
}
});
ObjectAssign(fields, ES.PrepareTemporalFields(item, entries));
return ES.DateFromFields(calendar, fields);
mergedFields = ES.PrepareTemporalFields(mergedFields, mergedEntries);
return ES.DateFromFields(calendar, mergedFields);
}
getISOFields() {
if (!ES.IsTemporalMonthDay(this)) throw new TypeError('invalid receiver');
Expand Down
25 changes: 17 additions & 8 deletions polyfill/lib/plainyearmonth.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import { DateTimeFormat } from './intl.mjs';
import { GetIntrinsic, MakeIntrinsicClass } from './intrinsicclass.mjs';
import { ISO_YEAR, ISO_MONTH, ISO_DAY, YEAR_MONTH_BRAND, CALENDAR, CreateSlots, GetSlot, SetSlot } from './slots.mjs';

const ObjectAssign = Object.assign;

function YearMonthToString(yearMonth, showCalendar = 'auto') {
const year = ES.ISOYearString(GetSlot(yearMonth, ISO_YEAR));
const month = ES.ISODateTimePartString(GetSlot(yearMonth, ISO_MONTH));
Expand Down Expand Up @@ -113,6 +111,7 @@ export class PlainYearMonth {
}
let fields = ES.ToTemporalYearMonthFields(this, fieldNames);
fields = ES.CalendarMergeFields(calendar, fields, props);
fields = ES.ToTemporalYearMonthFields(fields, fieldNames);

options = ES.NormalizeOptionsObject(options);

Expand Down Expand Up @@ -348,18 +347,28 @@ export class PlainYearMonth {
const calendar = GetSlot(this, CALENDAR);

const receiverFieldNames = ES.CalendarFields(calendar, ['monthCode', 'year']);
const fields = ES.ToTemporalYearMonthFields(this, receiverFieldNames);
let fields = ES.ToTemporalYearMonthFields(this, receiverFieldNames);

const inputFieldNames = ES.CalendarFields(calendar, ['day']);
const entries = [['day']];
const inputEntries = [['day']];
// Add extra fields from the calendar at the end
inputFieldNames.forEach((fieldName) => {
if (!entries.some(([name]) => name === fieldName)) {
entries.push([fieldName, undefined]);
if (!inputEntries.some(([name]) => name === fieldName)) {
inputEntries.push([fieldName, undefined]);
}
});
const inputFields = ES.PrepareTemporalFields(item, inputEntries);
let mergedFields = ES.CalendarMergeFields(calendar, fields, inputFields);

const mergedFieldNames = [...new Set([...receiverFieldNames, ...inputFieldNames])];
const mergedEntries = [];
mergedFieldNames.forEach((fieldName) => {
if (!mergedEntries.some(([name]) => name === fieldName)) {
mergedEntries.push([fieldName, undefined]);
}
});
ObjectAssign(fields, ES.PrepareTemporalFields(item, entries));
return ES.DateFromFields(calendar, fields, { overflow: 'reject' });
mergedFields = ES.PrepareTemporalFields(mergedFields, mergedEntries);
return ES.DateFromFields(calendar, mergedFields, { overflow: 'reject' });
}
getISOFields() {
if (!ES.IsTemporalYearMonth(this)) throw new TypeError('invalid receiver');
Expand Down
1 change: 1 addition & 0 deletions polyfill/lib/zoneddatetime.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ export class ZonedDateTime {
}
let fields = ES.ToTemporalZonedDateTimeFields(this, fieldNames);
fields = ES.CalendarMergeFields(calendar, fields, props);
fields = ES.ToTemporalZonedDateTimeFields(fields, fieldNames);
let {
year,
month,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright (C) 2021 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-temporal.plaindate.prototype.with
description: The object returned from mergeFields() is copied before being passed to dateFromFields().
info: |
sec-temporal.plaindate.prototype.with steps 13–15:
13. Set _fields_ to ? CalendarMergeFields(_calendar_, _fields_, _partialDate_).
14. Set _fields_ to ? PrepareTemporalFields(_fields_, _fieldNames_, «»).
15. Return ? DateFromFields(_calendar_, _fields_, _options_).
includes: [compareArray.js, temporalHelpers.js]
---*/

const expected = [
"get day",
"valueOf day",
"get month",
"valueOf month",
"get monthCode",
"toString monthCode",
"get year",
"valueOf year",
];

const calendar = TemporalHelpers.calendarMergeFieldsGetters();
const date = new Temporal.PlainDate(2021, 3, 31, calendar);
date.with({ year: 2022 });

assert.compareArray(calendar.mergeFieldsReturnOperations, expected, "getters called on mergeFields return");
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright (C) 2021 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-temporal.plaindatetime.prototype.with
description: The object returned from mergeFields() is copied before being passed to dateFromFields().
info: |
sec-temporal.plaindatetime.prototype.with steps 13–15:
13. Set _fields_ to ? CalendarMergeFields(_calendar_, _fields_, _partialDate_).
14. Set _fields_ to ? PrepareTemporalFields(_fields_, _fieldNames_, «»).
15. Let _result_ be ? InterpretTemporalDateTimeFields(_calendar_, _fields_, _options_).
sec-temporal-interprettemporaldatetimefields step 2:
2. Let _temporalDate_ be ? DateFromFields(_calendar_, _fields_, _options_).
includes: [compareArray.js, temporalHelpers.js]
---*/

const expected = [
"get day",
"valueOf day",
"get hour",
"valueOf hour",
"get microsecond",
"valueOf microsecond",
"get millisecond",
"valueOf millisecond",
"get minute",
"valueOf minute",
"get month",
"valueOf month",
"get monthCode",
"toString monthCode",
"get nanosecond",
"valueOf nanosecond",
"get second",
"valueOf second",
"get year",
"valueOf year",
];

const calendar = TemporalHelpers.calendarMergeFieldsGetters();
const datetime = new Temporal.PlainDateTime(2021, 3, 31, 12, 34, 56, 987, 654, 321, calendar);
datetime.with({ year: 2022 });

assert.compareArray(calendar.mergeFieldsReturnOperations, expected, "getters called on mergeFields return");
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (C) 2021 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-temporal.plainmonthday.prototype.toplaindate
description: The object returned from mergeFields() is copied before being passed to monthDayFromFields().
info: |
sec-temporal.plainmonthday.prototype.toplaindate steps 9 and 11:
9. Let _mergedFields_ be ? CalendarMergeFields(_calendar_, _fields_, _inputFields_).
11. Set _mergedFields_ to ? PrepareTemporalFields(_mergedFields_, _mergedFieldNames_, «»).
includes: [compareArray.js, temporalHelpers.js]
---*/

const expected = [
"get day",
"valueOf day",
"get monthCode",
"toString monthCode",
"get year",
"valueOf year",
];

const calendar = TemporalHelpers.calendarMergeFieldsGetters();
const monthday = new Temporal.PlainMonthDay(3, 31, calendar);
monthday.toPlainDate({ year: 2000 });

assert.compareArray(calendar.mergeFieldsReturnOperations, expected, "getters called on mergeFields return");
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright (C) 2021 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-temporal.plainmonthday.prototype.with
description: The object returned from mergeFields() is copied before being passed to monthDayFromFields().
info: |
sec-temporal.plainmonthday.prototype.with steps 13–15:
13. Set _fields_ to ? CalendarMergeFields(_calendar_, _fields_, _partialMonthDay_).
14. Set _fields_ to ? PrepareTemporalFields(_fields_, _fieldNames_, «»).
15. Return ? MonthDayFromFields(_calendar_, _fields_, _options_).
includes: [compareArray.js, temporalHelpers.js]
---*/

const expected = [
"get day",
"valueOf day",
"get month", // PlainMonthDay.month property does not exist, no valueOf
"get monthCode",
"toString monthCode",
"get year", // undefined, no valueOf
];

const calendar = TemporalHelpers.calendarMergeFieldsGetters();
const monthday = new Temporal.PlainMonthDay(3, 31, calendar);
monthday.with({ day: 1 });

assert.compareArray(calendar.mergeFieldsReturnOperations, expected, "getters called on mergeFields return");
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (C) 2021 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-temporal.plainyearmonth.prototype.toplaindate
description: The object returned from mergeFields() is copied before being passed to monthDayFromFields().
info: |
sec-temporal.plainyearmonth.prototype.toplaindate steps 9 and 11:
9. Let _mergedFields_ be ? CalendarMergeFields(_calendar_, _fields_, _inputFields_).
11. Set _mergedFields_ to ? PrepareTemporalFields(_mergedFields_, _mergedFieldNames_, «»).
includes: [compareArray.js, temporalHelpers.js]
---*/

const expected = [
"get monthCode",
"toString monthCode",
"get year",
"valueOf year",
"get day", // first receiver fields, then input fields
"valueOf day",
];

const calendar = TemporalHelpers.calendarMergeFieldsGetters();
const yearmonth = new Temporal.PlainYearMonth(2000, 5, calendar);
yearmonth.toPlainDate({ day: 2 });

assert.compareArray(calendar.mergeFieldsReturnOperations, expected, "getters called on mergeFields return");
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright (C) 2021 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-temporal.plainyearmonth.prototype.with
description: The object returned from mergeFields() is copied before being passed to monthDayFromFields().
info: |
sec-temporal.plainyearmonth.prototype.with steps 13–15:
13. Set _fields_ to ? CalendarMergeFields(_calendar_, _fields_, _partialYearMonth_).
14. Set _fields_ to ? PrepareTemporalFields(_fields_, _fieldNames_, «»).
15. Return ? YearMonthFromFields(_calendar_, _fields_, _options_).
includes: [compareArray.js, temporalHelpers.js]
---*/

const expected = [
"get month",
"valueOf month",
"get monthCode",
"toString monthCode",
"get year",
"valueOf year",
];

const calendar = TemporalHelpers.calendarMergeFieldsGetters();
const yearmonth = new Temporal.PlainYearMonth(2000, 5, calendar);
yearmonth.with({ year: 2004 });

assert.compareArray(calendar.mergeFieldsReturnOperations, expected, "getters called on mergeFields return");
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright (C) 2021 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-temporal.plaindatetime.prototype.with
description: The object returned from mergeFields() is copied before being passed to dateFromFields().
info: |
sec-temporal.plaindatetime.prototype.with steps 18–19 and 23:
18. Set _fields_ to ? CalendarMergeFields(_calendar_, _fields_, _partialDate_).
19. Set _fields_ to ? PrepareTemporalFields(_fields_, _fieldNames_, « *"timeZone"* »).
23. Let _dateTimeResult_ be ? InterpretTemporalDateTimeFields(_calendar_, _fields_, _options_).
sec-temporal-interprettemporaldatetimefields step 2:
2. Let _temporalDate_ be ? DateFromFields(_calendar_, _fields_, _options_).
includes: [compareArray.js, temporalHelpers.js]
---*/

const expected = [
"get day",
"valueOf day",
"get hour",
"valueOf hour",
"get microsecond",
"valueOf microsecond",
"get millisecond",
"valueOf millisecond",
"get minute",
"valueOf minute",
"get month",
"valueOf month",
"get monthCode",
"toString monthCode",
"get nanosecond",
"valueOf nanosecond",
"get second",
"valueOf second",
"get year",
"valueOf year",
"get offset",
"toString offset",
"get timeZone",
];

const calendar = TemporalHelpers.calendarMergeFieldsGetters();
const datetime = new Temporal.ZonedDateTime(1_000_000_000_000_000_000n, "UTC", calendar);
datetime.with({ year: 2022 });

assert.compareArray(calendar.mergeFieldsReturnOperations, expected, "getters called on mergeFields return");
Loading

0 comments on commit 863692f

Please sign in to comment.