Skip to content

Commit

Permalink
Ensure that implementation-defined Intl operations access properties …
Browse files Browse the repository at this point in the history
…in consistent order and only call getters once.

Fixes #1388.
  • Loading branch information
cjtenny committed Mar 8, 2021
1 parent 97b882c commit 50ba182
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 82 deletions.
86 changes: 35 additions & 51 deletions polyfill/lib/calendar.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -38,22 +38,19 @@ export class Calendar {
if (!ES.IsTemporalCalendar(this)) throw new TypeError('invalid receiver');
if (ES.Type(fields) !== 'Object') throw new TypeError('invalid fields');
options = ES.NormalizeOptionsObject(options);
const overflow = ES.ToTemporalOverflow(options);
return impl[GetSlot(this, CALENDAR_ID)].dateFromFields(fields, overflow, constructor, this);
return impl[GetSlot(this, CALENDAR_ID)].dateFromFields(fields, options, constructor, this);
}
yearMonthFromFields(fields, options, constructor) {
if (!ES.IsTemporalCalendar(this)) throw new TypeError('invalid receiver');
if (ES.Type(fields) !== 'Object') throw new TypeError('invalid fields');
options = ES.NormalizeOptionsObject(options);
const overflow = ES.ToTemporalOverflow(options);
return impl[GetSlot(this, CALENDAR_ID)].yearMonthFromFields(fields, overflow, constructor, this);
return impl[GetSlot(this, CALENDAR_ID)].yearMonthFromFields(fields, options, constructor, this);
}
monthDayFromFields(fields, options, constructor) {
if (!ES.IsTemporalCalendar(this)) throw new TypeError('invalid receiver');
if (ES.Type(fields) !== 'Object') throw new TypeError('invalid fields');
options = ES.NormalizeOptionsObject(options);
const overflow = ES.ToTemporalOverflow(options);
return impl[GetSlot(this, CALENDAR_ID)].monthDayFromFields(fields, overflow, constructor, this);
return impl[GetSlot(this, CALENDAR_ID)].monthDayFromFields(fields, options, constructor, this);
}
fields(fields) {
if (!ES.IsTemporalCalendar(this)) throw new TypeError('invalid receiver');
Expand Down Expand Up @@ -91,46 +88,58 @@ export class Calendar {
}
year(date) {
if (!ES.IsTemporalCalendar(this)) throw new TypeError('invalid receiver');
if (!HasSlot(date, ISO_YEAR)) date = ES.ToTemporalDate(date, GetIntrinsic('%Temporal.PlainDate%'));
return impl[GetSlot(this, CALENDAR_ID)].year(date);
}
month(date) {
if (!ES.IsTemporalCalendar(this)) throw new TypeError('invalid receiver');
if (ES.IsTemporalMonthDay(date)) throw new TypeError('use monthCode on PlainMonthDay instead');
if (!HasSlot(date, ISO_YEAR)) date = ES.ToTemporalDate(date, GetIntrinsic('%Temporal.PlainDate%'));
return impl[GetSlot(this, CALENDAR_ID)].month(date);
}
monthCode(date) {
if (!ES.IsTemporalCalendar(this)) throw new TypeError('invalid receiver');
if (!HasSlot(date, ISO_YEAR)) date = ES.ToTemporalDate(date, GetIntrinsic('%Temporal.PlainDate%'));
return impl[GetSlot(this, CALENDAR_ID)].monthCode(date);
}
day(date) {
if (!ES.IsTemporalCalendar(this)) throw new TypeError('invalid receiver');
if (!HasSlot(date, ISO_YEAR)) date = ES.ToTemporalDate(date, GetIntrinsic('%Temporal.PlainDate%'));
return impl[GetSlot(this, CALENDAR_ID)].day(date);
}
era(date) {
if (!ES.IsTemporalCalendar(this)) throw new TypeError('invalid receiver');
if (!HasSlot(date, ISO_YEAR)) date = ES.ToTemporalDate(date, GetIntrinsic('%Temporal.PlainDate%'));
return impl[GetSlot(this, CALENDAR_ID)].era(date);
}
eraYear(date) {
if (!ES.IsTemporalCalendar(this)) throw new TypeError('invalid receiver');
if (!HasSlot(date, ISO_YEAR)) date = ES.ToTemporalDate(date, GetIntrinsic('%Temporal.PlainDate%'));
return impl[GetSlot(this, CALENDAR_ID)].eraYear(date);
}
dayOfWeek(date) {
if (!ES.IsTemporalCalendar(this)) throw new TypeError('invalid receiver');
if (!HasSlot(date, ISO_YEAR)) date = ES.ToTemporalDate(date, GetIntrinsic('%Temporal.PlainDate%'));
return impl[GetSlot(this, CALENDAR_ID)].dayOfWeek(date);
}
dayOfYear(date) {
if (!ES.IsTemporalCalendar(this)) throw new TypeError('invalid receiver');
if (!HasSlot(date, ISO_YEAR)) date = ES.ToTemporalDate(date, GetIntrinsic('%Temporal.PlainDate%'));
return impl[GetSlot(this, CALENDAR_ID)].dayOfYear(date);
}
weekOfYear(date) {
if (!ES.IsTemporalCalendar(this)) throw new TypeError('invalid receiver');
if (!HasSlot(date, ISO_YEAR)) date = ES.ToTemporalDate(date, GetIntrinsic('%Temporal.PlainDate%'));
return impl[GetSlot(this, CALENDAR_ID)].weekOfYear(date);
}
daysInWeek(date) {
if (!ES.IsTemporalCalendar(this)) throw new TypeError('invalid receiver');
if (!HasSlot(date, ISO_YEAR)) date = ES.ToTemporalDate(date, GetIntrinsic('%Temporal.PlainDate%'));
return impl[GetSlot(this, CALENDAR_ID)].daysInWeek(date);
}
daysInMonth(date) {
if (!ES.IsTemporalCalendar(this)) throw new TypeError('invalid receiver');
if (!HasSlot(date, ISO_YEAR)) date = ES.ToTemporalDate(date, GetIntrinsic('%Temporal.PlainDate%'));
return impl[GetSlot(this, CALENDAR_ID)].daysInMonth(date);
}
daysInYear(date) {
Expand All @@ -139,6 +148,7 @@ export class Calendar {
}
monthsInYear(date) {
if (!ES.IsTemporalCalendar(this)) throw new TypeError('invalid receiver');
if (!HasSlot(date, ISO_YEAR)) date = ES.ToTemporalDate(date, GetIntrinsic('%Temporal.PlainDate%'));
return impl[GetSlot(this, CALENDAR_ID)].monthsInYear(date);
}
inLeapYear(date) {
Expand Down Expand Up @@ -175,21 +185,24 @@ MakeIntrinsicClass(Calendar, 'Temporal.Calendar');
DefineIntrinsic('Temporal.Calendar.from', Calendar.from);

impl['iso8601'] = {
dateFromFields(fields, overflow, constructor, calendar) {
dateFromFields(fields, options, constructor, calendar) {
const overflow = ES.ToTemporalOverflow(options);
fields = ES.PrepareTemporalFields(fields, [['day'], ['month', undefined], ['monthCode', undefined], ['year']]);
fields = resolveNonLunisolarMonth(fields);
let { year, month, day } = fields;
({ year, month, day } = ES.RegulateISODate(year, month, day, overflow));
return new constructor(year, month, day, calendar);
},
yearMonthFromFields(fields, overflow, constructor, calendar) {
yearMonthFromFields(fields, options, constructor, calendar) {
const overflow = ES.ToTemporalOverflow(options);
fields = ES.PrepareTemporalFields(fields, [['month', undefined], ['monthCode', undefined], ['year']]);
fields = resolveNonLunisolarMonth(fields);
let { year, month } = fields;
({ year, month } = ES.RegulateISOYearMonth(year, month, overflow));
return new constructor(year, month, calendar, /* referenceISODay */ 1);
},
monthDayFromFields(fields, overflow, constructor, calendar) {
monthDayFromFields(fields, options, constructor, calendar) {
const overflow = ES.ToTemporalOverflow(options);
fields = ES.PrepareTemporalFields(fields, [
['day'],
['month', undefined],
Expand Down Expand Up @@ -238,58 +251,43 @@ impl['iso8601'] = {
);
},
year(date) {
if (!HasSlot(date, ISO_YEAR)) date = ES.ToTemporalDate(date, GetIntrinsic('%Temporal.PlainDate%'));
return GetSlot(date, ISO_YEAR);
},
era(date) {
if (!HasSlot(date, ISO_YEAR)) date = ES.ToTemporalDate(date, GetIntrinsic('%Temporal.PlainDate%'));
era() {
return undefined;
},
eraYear(date) {
if (!HasSlot(date, ISO_YEAR)) date = ES.ToTemporalDate(date, GetIntrinsic('%Temporal.PlainDate%'));
eraYear() {
return undefined;
},
month(date) {
if (ES.IsTemporalMonthDay(date)) throw new TypeError('use monthCode on PlainMonthDay instead');
if (!HasSlot(date, ISO_MONTH)) date = ES.ToTemporalDate(date, GetIntrinsic('%Temporal.PlainDate%'));
return GetSlot(date, ISO_MONTH);
},
monthCode(date) {
if (!HasSlot(date, ISO_MONTH)) date = ES.ToTemporalDate(date, GetIntrinsic('%Temporal.PlainDate%'));
return buildMonthCode(GetSlot(date, ISO_MONTH));
},
day(date) {
if (!HasSlot(date, ISO_DAY)) date = ES.ToTemporalDate(date, GetIntrinsic('%Temporal.PlainDate%'));
return GetSlot(date, ISO_DAY);
},
dayOfWeek(date) {
date = ES.ToTemporalDate(date, GetIntrinsic('%Temporal.PlainDate%'));
return ES.DayOfWeek(GetSlot(date, ISO_YEAR), GetSlot(date, ISO_MONTH), GetSlot(date, ISO_DAY));
},
dayOfYear(date) {
date = ES.ToTemporalDate(date, GetIntrinsic('%Temporal.PlainDate%'));
return ES.DayOfYear(GetSlot(date, ISO_YEAR), GetSlot(date, ISO_MONTH), GetSlot(date, ISO_DAY));
},
weekOfYear(date) {
date = ES.ToTemporalDate(date, GetIntrinsic('%Temporal.PlainDate%'));
return ES.WeekOfYear(GetSlot(date, ISO_YEAR), GetSlot(date, ISO_MONTH), GetSlot(date, ISO_DAY));
},
daysInWeek(date) {
ES.ToTemporalDate(date, GetIntrinsic('%Temporal.PlainDate%'));
daysInWeek() {
return 7;
},
daysInMonth(date) {
if (!HasSlot(date, ISO_YEAR) || !HasSlot(date, ISO_MONTH)) {
date = ES.ToTemporalDate(date, GetIntrinsic('%Temporal.PlainDate%'));
}
return ES.ISODaysInMonth(GetSlot(date, ISO_YEAR), GetSlot(date, ISO_MONTH));
},
daysInYear(date) {
if (!HasSlot(date, ISO_YEAR)) date = ES.ToTemporalDate(date, GetIntrinsic('%Temporal.PlainDate%'));
return ES.LeapYear(GetSlot(date, ISO_YEAR)) ? 366 : 365;
},
monthsInYear(date) {
if (!HasSlot(date, ISO_YEAR)) ES.ToTemporalDate(date, GetIntrinsic('%Temporal.PlainDate%'));
monthsInYear() {
return 12;
},
inLeapYear(date) {
Expand Down Expand Up @@ -1779,7 +1777,8 @@ const helperDangi = ObjectAssign({}, { ...helperChinese, id: 'dangi' });
* ISO and non-ISO implementations vs. code that was very different.
*/
const nonIsoGeneralImpl = {
dateFromFields(fields, overflow, constructor, calendar) {
dateFromFields(fields, options, constructor, calendar) {
const overflow = ES.ToTemporalOverflow(options);
const cache = new OneObjectCache();
// Intentionally alphabetical
fields = ES.PrepareTemporalFields(fields, [
Expand All @@ -1795,7 +1794,8 @@ const nonIsoGeneralImpl = {
cache.setObject(result);
return result;
},
yearMonthFromFields(fields, overflow, constructor, calendar) {
yearMonthFromFields(fields, options, constructor, calendar) {
const overflow = ES.ToTemporalOverflow(options);
const cache = new OneObjectCache();
// Intentionally alphabetical
fields = ES.PrepareTemporalFields(fields, [
Expand All @@ -1810,19 +1810,20 @@ const nonIsoGeneralImpl = {
cache.setObject(result);
return result;
},
monthDayFromFields(fields, overflow, constructor, calendar) {
monthDayFromFields(fields, options, constructor, calendar) {
const overflow = ES.ToTemporalOverflow(options);
// All built-in calendars require `day`, but some allow other fields to be
// substituted for `month`. And for lunisolar calendars, either `monthCode`
// or `year` must be provided because `month` is ambiguous without a year or
// a code.
const cache = new OneObjectCache();
fields = ES.PrepareTemporalFields(fields, [
['day'],
['era', undefined],
['eraYear', undefined],
['month', undefined],
['year', undefined],
['monthCode', undefined],
['era', undefined],
['eraYear', undefined]
['year', undefined]
]);
const { year, month, day } = this.helper.monthDayFromFields(fields, overflow, cache);
// `year` is a reference year where this month/day exists in this calendar
Expand Down Expand Up @@ -1876,45 +1877,33 @@ const nonIsoGeneralImpl = {
return result;
},
year(date) {
if (!HasSlot(date, ISO_YEAR) || !HasSlot(date, ISO_MONTH) || !HasSlot(date, ISO_DAY)) {
date = ES.ToTemporalDate(date, GetIntrinsic('%Temporal.PlainDate%'));
}
const cache = OneObjectCache.getCacheForObject(date);
const calendarDate = this.helper.temporalToCalendarDate(date, cache);
return calendarDate.year;
},
month(date) {
if (!HasSlot(date, ISO_YEAR) || !HasSlot(date, ISO_MONTH) || !HasSlot(date, ISO_DAY)) {
date = ES.ToTemporalDate(date, GetIntrinsic('%Temporal.PlainDate%'));
}
const cache = OneObjectCache.getCacheForObject(date);
const calendarDate = this.helper.temporalToCalendarDate(date, cache);
return calendarDate.month;
},
day(date) {
if (!HasSlot(date, ISO_YEAR) || !HasSlot(date, ISO_MONTH) || !HasSlot(date, ISO_DAY)) {
date = ES.ToTemporalDate(date, GetIntrinsic('%Temporal.PlainDate%'));
}
const cache = OneObjectCache.getCacheForObject(date);
const calendarDate = this.helper.temporalToCalendarDate(date, cache);
return calendarDate.day;
},
era(date) {
if (!HasSlot(date, ISO_YEAR)) date = ES.ToTemporalDate(date, GetIntrinsic('%Temporal.PlainDate%'));
if (!this.helper.hasEra) return undefined;
const cache = OneObjectCache.getCacheForObject(date);
const calendarDate = this.helper.temporalToCalendarDate(date, cache);
return calendarDate.era;
},
eraYear(date) {
if (!HasSlot(date, ISO_YEAR)) date = ES.ToTemporalDate(date, GetIntrinsic('%Temporal.PlainDate%'));
if (!this.helper.hasEra) return undefined;
const cache = OneObjectCache.getCacheForObject(date);
const calendarDate = this.helper.temporalToCalendarDate(date, cache);
return calendarDate.eraYear;
},
monthCode(date) {
if (!HasSlot(date, ISO_MONTH)) date = ES.ToTemporalDate(date, GetIntrinsic('%Temporal.PlainDate%'));
const cache = OneObjectCache.getCacheForObject(date);
const calendarDate = this.helper.temporalToCalendarDate(date, cache);
return calendarDate.monthCode;
Expand All @@ -1924,7 +1913,6 @@ const nonIsoGeneralImpl = {
},
dayOfYear(date) {
const cache = OneObjectCache.getCacheForObject(date);
date = ES.ToTemporalDate(date, GetIntrinsic('%Temporal.PlainDate%'));
const calendarDate = this.helper.isoToCalendarDate(date, cache);
const startOfYear = this.helper.startOfCalendarYear(calendarDate);
const diffDays = this.helper.calendarDaysUntil(startOfYear, calendarDate, cache);
Expand All @@ -1937,9 +1925,6 @@ const nonIsoGeneralImpl = {
return impl['iso8601'].daysInWeek(date);
},
daysInMonth(date) {
if (!HasSlot(date, ISO_YEAR) || !HasSlot(date, ISO_MONTH)) {
date = ES.ToTemporalDate(date, GetIntrinsic('%Temporal.PlainDate%'));
}
const cache = OneObjectCache.getCacheForObject(date);
const calendarDate = this.helper.temporalToCalendarDate(date, cache);

Expand All @@ -1966,7 +1951,6 @@ const nonIsoGeneralImpl = {
return result;
},
monthsInYear(date) {
if (!HasSlot(date, ISO_YEAR)) ES.ToTemporalDate(date, GetIntrinsic('%Temporal.PlainDate%'));
const cache = OneObjectCache.getCacheForObject(date);
const calendarDate = this.helper.temporalToCalendarDate(date, cache);
const result = this.helper.monthsInYear(calendarDate, cache);
Expand Down
4 changes: 2 additions & 2 deletions polyfill/lib/ecmascript.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1010,7 +1010,7 @@ export const ES = ObjectAssign({}, ES2020, {
}
return any ? any : false;
},
PrepareTemporalFields: (bag, fields) => {
PrepareTemporalFields: (bag, fields, convert = true) => {
if (ES.Type(bag) !== 'Object') return false;
const result = {};
let any = false;
Expand All @@ -1024,7 +1024,7 @@ export const ES = ObjectAssign({}, ES2020, {
value = defaultValue;
} else {
any = true;
if (BUILTIN_CASTS.has(property)) {
if (convert && BUILTIN_CASTS.has(property)) {
value = BUILTIN_CASTS.get(property)(value, property);
}
}
Expand Down
14 changes: 7 additions & 7 deletions spec/calendar.html
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,7 @@ <h1>ISOYear ( _dateOrDateTime_ )</h1>
The ISOYear abstract operation implements the calendar-specific logic in the `Temporal.Calendar.year` method for the ISO 8601 calendar.
</p>
<emu-alg>
1. If _dateOrDateTime_ does not have an [[ISOYear]] internal slot, then
1. If Type(_dateOrDateTime_) is not Object or _dateOrDateTime_ does not have an [[ISOYear]] internal slot, then
1. Set _dateOrDateTime_ to ? ToTemporalDate(_dateOrDateTime_).
1. Return 𝔽(_dateOrDateTime_.[[ISOYear]]).
</emu-alg>
Expand All @@ -536,9 +536,7 @@ <h1>ISOMonth ( _dateOrDateTime_ )</h1>
The ISOMonth abstract operation implements the calendar-specific logic in the `Temporal.Calendar.month` method for the ISO 8601 calendar.
</p>
<emu-alg>
1. If _dateOrDateTime_ has an [[InitializedTemporalMonthDay]] internal slot, then
1. Throw a *TypeError* exception.
1. If _dateOrDateTime_ does not have an [[ISOMonth]] internal slot, then
1. If Type(_dateOrDateTime_) is not Object or _dateOrDateTime_ does not have an [[ISOMonth]] internal slot, then
1. Set _dateOrDateTime_ to ? ToTemporalDate(_dateOrDateTime_).
1. Return 𝔽(_dateOrDateTime_.[[ISOMonth]]).
</emu-alg>
Expand All @@ -550,7 +548,7 @@ <h1>ISOMonthCode ( _dateOrDateTime_ )</h1>
The ISOMonthCode abstract operation implements the calendar-specific logic in the `Temporal.Calendar.monthCode` method for the ISO 8601 calendar.
</p>
<emu-alg>
1. If _dateOrDateTime_ does not have an [[ISOMonth]] internal slot, then
1. If Type(_dateOrDateTime_) is not Object or _dateOrDateTime_ does not have an [[ISOMonth]] internal slot, then
1. Set _dateOrDateTime_ to ? ToTemporalDate(_dateOrDateTime_).
1. Let _monthCode_ be the String representation of _dateOrDateTime_.[[ISOMonth]], formatted as a two-digit decimal number, padded to the left with the code unit 0x0030 (DIGIT ZERO) if necessary.
1. Return the string-concatenation of *"M"* and _monthCode_.
Expand All @@ -563,7 +561,7 @@ <h1>ISODay ( _dateOrDateTime_ )</h1>
The ISODay abstract operation implements the calendar-specific logic in the `Temporal.Calendar.day` method for the ISO 8601 calendar.
</p>
<emu-alg>
1. If _dateOrDateTime_ does not have an [[ISODay]] internal slot, then
1. If Type(_dateOrDateTime_) is not Object or _dateOrDateTime_ does not have an [[ISODay]] internal slot, then
1. Set _dateOrDateTime_ to ? ToTemporalDate(_dateOrDateTime_).
1. Return 𝔽(_dateOrDateTime_.[[ISODay]]).
</emu-alg>
Expand Down Expand Up @@ -854,6 +852,8 @@ <h1>Temporal.Calendar.prototype.month ( _dateOrDateTime_ )</h1>
<emu-alg>
1. Let _calendar_ be the *this* value.
1. Perform ? RequireInternalSlot(_calendar_, [[InitializedTemporalCalendar]]).
1. If Type(_dateOrDateTime_) is Object and _dateOrDateTime_ has an [[InitializedTemporalMonthDay]] internal slot, then
1. Throw a *TypeError* exception.
1. Assert: _calendar_.[[Identifier]] is *"iso8601"*.
1. Return ? ISOMonth(_dateOrDateTime_).
</emu-alg>
Expand Down Expand Up @@ -1024,7 +1024,7 @@ <h1>Temporal.Calendar.prototype.monthsInYear ( _dateOrDateTime_ )</h1>
1. Let _calendar_ be the *this* value.
1. Perform ? RequireInternalSlot(_calendar_, [[InitializedTemporalCalendar]]).
1. Assert: _calendar_.[[Identifier]] is *"iso8601"*.
1. Perform ? ToTemporalDateTime(_dateOrDateTime_).
1. Perform ? ToTemporalDate(_dateOrDateTime_).
1. Return *12*<sub>𝔽</sub>.
</emu-alg>
</emu-clause>
Expand Down
Loading

0 comments on commit 50ba182

Please sign in to comment.