Skip to content

Commit

Permalink
Add internal create methods to avoid observable effects
Browse files Browse the repository at this point in the history
The constructors of PlainDate, PlainDateTime, PlainMonthDay,
PlainYearMonth, and ZonedDateTime call ToTemporalCalendar on the calendar
argument, and the constructor of ZonedDateTime additionally calls
ToTemporalTimeZone on its timeZone argument. Now that these operations
include an observable HasProperty operation, we do not want to call them
more than once on the same calendar or time zone object.

Therefore we add a private Symbol ES.PrivateCreateMethod, which hides
internal static methods on these types, which create an instance without
any observable validation of the arguments.

The spec text already works like this, with abstract operations such as
CreateTemporalZonedDateTime and friends that do not call
ToTemporalCalendar or ToTemporalTimeZone. So this is not a normative
change; it's a compliance bug in the polyfill. It is required for the
test262 tests in 19cd26f to pass, which observe HasProperty(`timeZone`)
operations.

See: #1428
  • Loading branch information
ptomato committed Apr 15, 2021
1 parent 7ab267b commit c9f30e2
Show file tree
Hide file tree
Showing 10 changed files with 347 additions and 169 deletions.
16 changes: 8 additions & 8 deletions polyfill/lib/calendar.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ impl['iso8601'] = {
let { year, month, day } = fields;
({ year, month, day } = ES.RegulateISODate(year, month, day, overflow));
const TemporalPlainDate = GetIntrinsic('%Temporal.PlainDate%');
return new TemporalPlainDate(year, month, day, calendar);
return TemporalPlainDate[ES.PrivateCreateMethod](year, month, day, calendar);
},
yearMonthFromFields(fields, options, calendar) {
const overflow = ES.ToTemporalOverflow(options);
Expand All @@ -189,7 +189,7 @@ impl['iso8601'] = {
let { year, month } = fields;
({ year, month } = ES.RegulateISOYearMonth(year, month, overflow));
const TemporalPlainYearMonth = GetIntrinsic('%Temporal.PlainYearMonth%');
return new TemporalPlainYearMonth(year, month, calendar, /* referenceISODay */ 1);
return TemporalPlainYearMonth[ES.PrivateCreateMethod](year, month, calendar, /* referenceISODay = */ 1);
},
monthDayFromFields(fields, options, calendar) {
const overflow = ES.ToTemporalOverflow(options);
Expand All @@ -208,7 +208,7 @@ impl['iso8601'] = {
let { month, day, year } = fields;
({ month, day } = ES.RegulateISODate(useYear ? year : referenceISOYear, month, day, overflow));
const TemporalPlainMonthDay = GetIntrinsic('%Temporal.PlainMonthDay%');
return new TemporalPlainMonthDay(month, day, calendar, referenceISOYear);
return TemporalPlainMonthDay[ES.PrivateCreateMethod](month, day, calendar, referenceISOYear);
},
fields(fields) {
return fields;
Expand All @@ -229,7 +229,7 @@ impl['iso8601'] = {
let day = GetSlot(date, ISO_DAY);
({ year, month, day } = ES.AddISODate(year, month, day, years, months, weeks, days, overflow));
const TemporalPlainDate = GetIntrinsic('%Temporal.PlainDate%');
return new TemporalPlainDate(year, month, day, calendar);
return TemporalPlainDate[ES.PrivateCreateMethod](year, month, day, calendar);
},
dateUntil(one, two, largestUnit) {
return ES.DifferenceISODate(
Expand Down Expand Up @@ -1783,7 +1783,7 @@ const nonIsoGeneralImpl = {
]);
const { year, month, day } = this.helper.calendarToIsoDate(fields, overflow, cache);
const TemporalPlainDate = GetIntrinsic('%Temporal.PlainDate%');
const result = new TemporalPlainDate(year, month, day, calendar);
const result = TemporalPlainDate[ES.PrivateCreateMethod](year, month, day, calendar);
cache.setObject(result);
return result;
},
Expand All @@ -1800,7 +1800,7 @@ const nonIsoGeneralImpl = {
]);
const { year, month, day } = this.helper.calendarToIsoDate({ ...fields, day: 1 }, overflow, cache);
const TemporalPlainYearMonth = GetIntrinsic('%Temporal.PlainYearMonth%');
const result = new TemporalPlainYearMonth(year, month, calendar, /* referenceISODay = */ day);
const result = TemporalPlainYearMonth[ES.PrivateCreateMethod](year, month, calendar, /* referenceISODay = */ day);
cache.setObject(result);
return result;
},
Expand All @@ -1822,7 +1822,7 @@ const nonIsoGeneralImpl = {
const { year, month, day } = this.helper.monthDayFromFields(fields, overflow, cache);
// `year` is a reference year where this month/day exists in this calendar
const TemporalPlainMonthDay = GetIntrinsic('%Temporal.PlainMonthDay%');
const result = new TemporalPlainMonthDay(month, day, calendar, /* referenceISOYear */ year);
const result = TemporalPlainMonthDay[ES.PrivateCreateMethod](month, day, calendar, /* referenceISOYear = */ year);
cache.setObject(result);
return result;
},
Expand Down Expand Up @@ -1858,7 +1858,7 @@ const nonIsoGeneralImpl = {
const isoAdded = this.helper.calendarToIsoDate(added, 'constrain', cache);
const { year, month, day } = isoAdded;
const TemporalPlainDate = GetIntrinsic('%Temporal.PlainDate%');
const newTemporalObject = new TemporalPlainDate(year, month, day, calendar);
const newTemporalObject = TemporalPlainDate[ES.PrivateCreateMethod](year, month, day, calendar);
// The new object's cache starts with the cache of the old object
const newCache = new OneObjectCache(cache);
newCache.setObject(newTemporalObject);
Expand Down
76 changes: 53 additions & 23 deletions polyfill/lib/ecmascript.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ const ES2020 = {
};

export const ES = ObjectAssign({}, ES2020, {
// Temporal.Type[ES.PrivateCreateMethod] is used internally to create an
// instance of a type that carries a calendar or time zone while bypassing the
// validation checks on the calendar and/or time zone, which may be observable
PrivateCreateMethod: Symbol('PrivateCreateMethod'),
ToPositiveInteger: ToPositiveInteger,
IsTemporalInstant: (item) => HasSlot(item, EPOCHNANOSECONDS) && !HasSlot(item, TIME_ZONE, CALENDAR),
IsTemporalTimeZone: (item) => HasSlot(item, TIMEZONE_ID),
Expand Down Expand Up @@ -863,7 +867,7 @@ export const ES = ObjectAssign({}, ES2020, {
if (ES.IsTemporalZonedDateTime(relativeTo) || ES.IsTemporalDateTime(relativeTo)) return relativeTo;
if (ES.IsTemporalDate(relativeTo)) {
const TemporalDateTime = GetIntrinsic('%Temporal.PlainDateTime%');
return new TemporalDateTime(
return TemporalDateTime[ES.PrivateCreateMethod](
GetSlot(relativeTo, ISO_YEAR),
GetSlot(relativeTo, ISO_MONTH),
GetSlot(relativeTo, ISO_DAY),
Expand Down Expand Up @@ -932,10 +936,21 @@ export const ES = ObjectAssign({}, ES2020, {
'reject'
);
const TemporalZonedDateTime = GetIntrinsic('%Temporal.ZonedDateTime%');
return new TemporalZonedDateTime(epochNanoseconds, timeZone, calendar);
return TemporalZonedDateTime[ES.PrivateCreateMethod](epochNanoseconds, timeZone, calendar);
}
const TemporalDateTime = GetIntrinsic('%Temporal.PlainDateTime%');
return new TemporalDateTime(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond, calendar);
return TemporalDateTime[ES.PrivateCreateMethod](
year,
month,
day,
hour,
minute,
second,
millisecond,
microsecond,
nanosecond,
calendar
);
},
ValidateTemporalUnitRange: (largestUnit, smallestUnit) => {
const validUnits = [
Expand Down Expand Up @@ -1162,7 +1177,7 @@ export const ES = ObjectAssign({}, ES2020, {
}
if (ES.IsTemporalDateTime(item)) {
const TemporalPlainDate = GetIntrinsic('%Temporal.PlainDate%');
return new TemporalPlainDate(
return TemporalPlainDate[ES.PrivateCreateMethod](
GetSlot(item, ISO_YEAR),
GetSlot(item, ISO_MONTH),
GetSlot(item, ISO_DAY),
Expand All @@ -1178,7 +1193,6 @@ export const ES = ObjectAssign({}, ES2020, {
let { year, month, day, calendar } = ES.ParseTemporalDateString(ES.ToString(item));
ES.RejectISODate(year, month, day);
if (calendar === undefined) calendar = ES.GetISO8601Calendar();
calendar = ES.ToTemporalCalendar(calendar);
const TemporalPlainDate = GetIntrinsic('%Temporal.PlainDate%');
return new TemporalPlainDate(year, month, day, calendar);
},
Expand Down Expand Up @@ -1213,7 +1227,7 @@ export const ES = ObjectAssign({}, ES2020, {
}
if (ES.IsTemporalDate(item)) {
const TemporalPlainDateTime = GetIntrinsic('%Temporal.PlainDateTime%');
return new TemporalPlainDateTime(
return TemporalPlainDateTime[ES.PrivateCreateMethod](
GetSlot(item, ISO_YEAR),
GetSlot(item, ISO_MONTH),
GetSlot(item, ISO_DAY),
Expand Down Expand Up @@ -1260,7 +1274,7 @@ export const ES = ObjectAssign({}, ES2020, {
calendar = ES.ToTemporalCalendar(calendar);
}
const TemporalPlainDateTime = GetIntrinsic('%Temporal.PlainDateTime%');
return new TemporalPlainDateTime(
return TemporalPlainDateTime[ES.PrivateCreateMethod](
year,
month,
day,
Expand Down Expand Up @@ -1359,9 +1373,9 @@ export const ES = ObjectAssign({}, ES2020, {
const TemporalPlainMonthDay = GetIntrinsic('%Temporal.PlainMonthDay%');
if (referenceISOYear === undefined) {
ES.RejectISODate(1972, month, day);
return new TemporalPlainMonthDay(month, day, calendar);
return TemporalPlainMonthDay[ES.PrivateCreateMethod](month, day, calendar);
}
const result = new TemporalPlainMonthDay(month, day, calendar, referenceISOYear);
const result = TemporalPlainMonthDay[ES.PrivateCreateMethod](month, day, calendar, referenceISOYear);
return ES.MonthDayFromFields(calendar, result, {});
},
ToTemporalTime: (item, overflow = 'constrain') => {
Expand Down Expand Up @@ -1429,9 +1443,9 @@ export const ES = ObjectAssign({}, ES2020, {
const TemporalPlainYearMonth = GetIntrinsic('%Temporal.PlainYearMonth%');
if (referenceISODay === undefined) {
ES.RejectISODate(year, month, 1);
return new TemporalPlainYearMonth(year, month, calendar);
return TemporalPlainYearMonth[ES.PrivateCreateMethod](year, month, calendar);
}
const result = new TemporalPlainYearMonth(year, month, calendar, referenceISODay);
const result = TemporalPlainYearMonth[ES.PrivateCreateMethod](year, month, calendar, referenceISODay);
return ES.YearMonthFromFields(calendar, result, {});
},
InterpretISODateTimeOffset: (
Expand Down Expand Up @@ -1562,7 +1576,7 @@ export const ES = ObjectAssign({}, ES2020, {
offsetOpt
);
const TemporalZonedDateTime = GetIntrinsic('%Temporal.ZonedDateTime%');
return new TemporalZonedDateTime(epochNanoseconds, timeZone, calendar);
return TemporalZonedDateTime[ES.PrivateCreateMethod](epochNanoseconds, timeZone, calendar);
},

GetISO8601Calendar: () => {
Expand Down Expand Up @@ -1763,7 +1777,7 @@ export const ES = ObjectAssign({}, ES2020, {
},
TemporalDateTimeToDate: (dateTime) => {
const Date = GetIntrinsic('%Temporal.PlainDate%');
return new Date(
return Date[ES.PrivateCreateMethod](
GetSlot(dateTime, ISO_YEAR),
GetSlot(dateTime, ISO_MONTH),
GetSlot(dateTime, ISO_DAY),
Expand Down Expand Up @@ -1815,7 +1829,18 @@ export const ES = ObjectAssign({}, ES2020, {
nanosecond + offsetNs
));
const PlainDateTime = GetIntrinsic('%Temporal.PlainDateTime%');
return new PlainDateTime(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond, calendar);
return PlainDateTime[ES.PrivateCreateMethod](
year,
month,
day,
hour,
minute,
second,
millisecond,
microsecond,
nanosecond,
calendar
);
},
BuiltinTimeZoneGetInstantFor: (timeZone, dateTime, disambiguation) => {
const Instant = GetIntrinsic('%Temporal.Instant%');
Expand Down Expand Up @@ -3027,8 +3052,8 @@ export const ES = ObjectAssign({}, ES2020, {
ns2
);
({ year: y1, month: mon1, day: d1 } = ES.BalanceISODate(y1, mon1, d1 + deltaDays));
const date1 = new TemporalDate(y1, mon1, d1, calendar);
const date2 = new TemporalDate(y2, mon2, d2, calendar);
const date1 = TemporalDate[ES.PrivateCreateMethod](y1, mon1, d1, calendar);
const date2 = TemporalDate[ES.PrivateCreateMethod](y2, mon2, d2, calendar);
const dateLargestUnit = ES.LargerOfTwoTemporalDurationUnits('days', largestUnit);
const untilOptions = { ...options, largestUnit: dateLargestUnit };
let { years, months, weeks, days } = ES.CalendarDateUntil(calendar, date1, date2, untilOptions);
Expand Down Expand Up @@ -3095,7 +3120,7 @@ export const ES = ObjectAssign({}, ES2020, {
// may disambiguate
let timeRemainderNs = ns2.subtract(intermediateNs);
const TemporalZonedDateTime = GetIntrinsic('%Temporal.ZonedDateTime%');
const intermediate = new TemporalZonedDateTime(intermediateNs, timeZone, calendar);
const intermediate = TemporalZonedDateTime[ES.PrivateCreateMethod](intermediateNs, timeZone, calendar);
({ nanoseconds: timeRemainderNs, days } = ES.NanosecondsToDays(timeRemainderNs, intermediate));

// Finally, merge the date and time durations and return the merged result.
Expand Down Expand Up @@ -3210,7 +3235,7 @@ export const ES = ObjectAssign({}, ES2020, {
const TemporalDuration = GetIntrinsic('%Temporal.Duration%');
const calendar = GetSlot(relativeTo, CALENDAR);

const datePart = new TemporalPlainDate(
const datePart = TemporalPlainDate[ES.PrivateCreateMethod](
GetSlot(relativeTo, ISO_YEAR),
GetSlot(relativeTo, ISO_MONTH),
GetSlot(relativeTo, ISO_DAY),
Expand Down Expand Up @@ -3371,7 +3396,7 @@ export const ES = ObjectAssign({}, ES2020, {
// Delegate the date part addition to the calendar
const TemporalDate = GetIntrinsic('%Temporal.PlainDate%');
const TemporalDuration = GetIntrinsic('%Temporal.Duration%');
const datePart = new TemporalDate(year, month, day, calendar);
const datePart = TemporalDate[ES.PrivateCreateMethod](year, month, day, calendar);
const dateDuration = new TemporalDuration(years, months, weeks, days, 0, 0, 0, 0, 0, 0);
const addedDate = ES.CalendarDateAdd(calendar, datePart, dateDuration, options);

Expand Down Expand Up @@ -3405,11 +3430,16 @@ export const ES = ObjectAssign({}, ES2020, {
// time portion to be added in exact time.
let dt = ES.BuiltinTimeZoneGetPlainDateTimeFor(timeZone, instant, calendar);
const TemporalDate = GetIntrinsic('%Temporal.PlainDate%');
const datePart = new TemporalDate(GetSlot(dt, ISO_YEAR), GetSlot(dt, ISO_MONTH), GetSlot(dt, ISO_DAY), calendar);
const datePart = TemporalDate[ES.PrivateCreateMethod](
GetSlot(dt, ISO_YEAR),
GetSlot(dt, ISO_MONTH),
GetSlot(dt, ISO_DAY),
calendar
);
const dateDuration = new TemporalDuration(years, months, weeks, days, 0, 0, 0, 0, 0, 0);
const addedDate = ES.CalendarDateAdd(calendar, datePart, dateDuration, options);
const TemporalDateTime = GetIntrinsic('%Temporal.PlainDateTime%');
const dtIntermediate = new TemporalDateTime(
const dtIntermediate = TemporalDateTime[ES.PrivateCreateMethod](
GetSlot(addedDate, ISO_YEAR),
GetSlot(addedDate, ISO_MONTH),
GetSlot(addedDate, ISO_DAY),
Expand Down Expand Up @@ -3556,7 +3586,7 @@ export const ES = ObjectAssign({}, ES2020, {
const PlainDateTime = GetIntrinsic('%Temporal.PlainDateTime%');
const later = ES.CalendarDateAdd(calendar, relativeTo, duration, {});
const days = ES.DaysUntil(relativeTo, later);
relativeTo = new PlainDateTime(
relativeTo = PlainDateTime[ES.PrivateCreateMethod](
GetSlot(later, ISO_YEAR),
GetSlot(later, ISO_MONTH),
GetSlot(later, ISO_DAY),
Expand Down Expand Up @@ -3589,7 +3619,7 @@ export const ES = ObjectAssign({}, ES2020, {
0
);
const TemporalZonedDateTime = GetIntrinsic('%Temporal.ZonedDateTime%');
return new TemporalZonedDateTime(intermediateNs, timeZone, calendar);
return TemporalZonedDateTime[ES.PrivateCreateMethod](intermediateNs, timeZone, calendar);
},
AdjustRoundedDurationDays: (
years,
Expand Down
4 changes: 2 additions & 2 deletions polyfill/lib/instant.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ export class Instant {
}
const timeZone = ES.ToTemporalTimeZone(temporalTimeZoneLike);
const TemporalZonedDateTime = GetIntrinsic('%Temporal.ZonedDateTime%');
return new TemporalZonedDateTime(GetSlot(this, EPOCHNANOSECONDS), timeZone, calendar);
return TemporalZonedDateTime[ES.PrivateCreateMethod](GetSlot(this, EPOCHNANOSECONDS), timeZone, calendar);
}
toZonedDateTimeISO(item) {
if (!ES.IsTemporalInstant(this)) throw new TypeError('invalid receiver');
Expand All @@ -257,7 +257,7 @@ export class Instant {
const timeZone = ES.ToTemporalTimeZone(item);
const calendar = ES.GetISO8601Calendar();
const TemporalZonedDateTime = GetIntrinsic('%Temporal.ZonedDateTime%');
return new TemporalZonedDateTime(GetSlot(this, EPOCHNANOSECONDS), timeZone, calendar);
return TemporalZonedDateTime[ES.PrivateCreateMethod](GetSlot(this, EPOCHNANOSECONDS), timeZone, calendar);
}

static fromEpochSeconds(epochSeconds) {
Expand Down
7 changes: 2 additions & 5 deletions polyfill/lib/now.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,10 @@ function zonedDateTime(calendarLike, temporalTimeZoneLike = timeZone()) {
const timeZone = ES.ToTemporalTimeZone(temporalTimeZoneLike);
const calendar = ES.ToTemporalCalendar(calendarLike);
const ZonedDateTime = GetIntrinsic('%Temporal.ZonedDateTime%');
return new ZonedDateTime(ES.SystemUTCEpochNanoSeconds(), timeZone, calendar);
return ZonedDateTime[ES.PrivateCreateMethod](ES.SystemUTCEpochNanoSeconds(), timeZone, calendar);
}
function zonedDateTimeISO(temporalTimeZoneLike = timeZone()) {
const timeZone = ES.ToTemporalTimeZone(temporalTimeZoneLike);
const calendar = ES.GetISO8601Calendar();
const ZonedDateTime = GetIntrinsic('%Temporal.ZonedDateTime%');
return new ZonedDateTime(ES.SystemUTCEpochNanoSeconds(), timeZone, calendar);
return zonedDateTime(ES.GetISO8601Calendar(), temporalTimeZoneLike);
}
function plainDate(calendarLike, temporalTimeZoneLike = timeZone()) {
return ES.TemporalDateTimeToDate(plainDateTime(calendarLike, temporalTimeZoneLike));
Expand Down
Loading

0 comments on commit c9f30e2

Please sign in to comment.