Skip to content

Commit

Permalink
Validate required methods of Temporal Calendar protocol
Browse files Browse the repository at this point in the history
Checking whether an object implements the Calendar protocol is now done by
means of HasProperty operations for each of the required methods unless
the object already has the Calendar brand.

Discussion:
tc39/proposal-temporal#2104 (comment)

Corresponding normative PR:
tc39/proposal-temporal#2485
  • Loading branch information
ptomato committed Apr 7, 2023
1 parent 238483e commit 80b37da
Show file tree
Hide file tree
Showing 190 changed files with 3,422 additions and 303 deletions.
66 changes: 61 additions & 5 deletions harness/temporalHelpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -1464,6 +1464,34 @@ var TemporalHelpers = {
* objectName is used in the log.
*/
calendarObserver(calls, objectName, methodOverrides = {}) {
function removeExtraHasPropertyChecks(objectName, calls) {
// Inserting the tracking calendar into the return values of methods
// that we chain up into the ISO calendar for, causes extra HasProperty
// checks, which we observe. This removes them so that we don't leak
// implementation details of the helper into the test code.
assert.sameValue(calls.pop(), `has ${objectName}.yearOfWeek`);
assert.sameValue(calls.pop(), `has ${objectName}.yearMonthFromFields`);
assert.sameValue(calls.pop(), `has ${objectName}.year`);
assert.sameValue(calls.pop(), `has ${objectName}.weekOfYear`);
assert.sameValue(calls.pop(), `has ${objectName}.monthsInYear`);
assert.sameValue(calls.pop(), `has ${objectName}.monthDayFromFields`);
assert.sameValue(calls.pop(), `has ${objectName}.monthCode`);
assert.sameValue(calls.pop(), `has ${objectName}.month`);
assert.sameValue(calls.pop(), `has ${objectName}.mergeFields`);
assert.sameValue(calls.pop(), `has ${objectName}.inLeapYear`);
assert.sameValue(calls.pop(), `has ${objectName}.id`);
assert.sameValue(calls.pop(), `has ${objectName}.fields`);
assert.sameValue(calls.pop(), `has ${objectName}.daysInYear`);
assert.sameValue(calls.pop(), `has ${objectName}.daysInWeek`);
assert.sameValue(calls.pop(), `has ${objectName}.daysInMonth`);
assert.sameValue(calls.pop(), `has ${objectName}.dayOfYear`);
assert.sameValue(calls.pop(), `has ${objectName}.dayOfWeek`);
assert.sameValue(calls.pop(), `has ${objectName}.day`);
assert.sameValue(calls.pop(), `has ${objectName}.dateUntil`);
assert.sameValue(calls.pop(), `has ${objectName}.dateFromFields`);
assert.sameValue(calls.pop(), `has ${objectName}.dateAdd`);
}

const iso8601 = new Temporal.Calendar("iso8601");
const trackingMethods = {
dateFromFields(...args) {
Expand All @@ -1475,7 +1503,9 @@ var TemporalHelpers = {
const originalResult = iso8601.dateFromFields(...args);
// Replace the calendar in the result with the call-tracking calendar
const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
return new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
removeExtraHasPropertyChecks(objectName, calls);
return result;
},
yearMonthFromFields(...args) {
calls.push(`call ${objectName}.yearMonthFromFields`);
Expand All @@ -1486,7 +1516,9 @@ var TemporalHelpers = {
const originalResult = iso8601.yearMonthFromFields(...args);
// Replace the calendar in the result with the call-tracking calendar
const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
return new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay);
removeExtraHasPropertyChecks(objectName, calls);
return result;
},
monthDayFromFields(...args) {
calls.push(`call ${objectName}.monthDayFromFields`);
Expand All @@ -1497,7 +1529,9 @@ var TemporalHelpers = {
const originalResult = iso8601.monthDayFromFields(...args);
// Replace the calendar in the result with the call-tracking calendar
const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
return new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear);
removeExtraHasPropertyChecks(objectName, calls);
return result;
},
dateAdd(...args) {
calls.push(`call ${objectName}.dateAdd`);
Expand All @@ -1507,12 +1541,34 @@ var TemporalHelpers = {
}
const originalResult = iso8601.dateAdd(...args);
const {isoYear, isoMonth, isoDay} = originalResult.getISOFields();
return new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this);
removeExtraHasPropertyChecks(objectName, calls);
return result;
},
id: "iso8601",
};
// Automatically generate the other methods that don't need any custom code
["toString", "dateUntil", "era", "eraYear", "year", "month", "monthCode", "day", "daysInMonth", "fields", "mergeFields"].forEach((methodName) => {
[
"dateUntil",
"day",
"dayOfWeek",
"dayOfYear",
"daysInMonth",
"daysInWeek",
"daysInYear",
"era",
"eraYear",
"fields",
"inLeapYear",
"mergeFields",
"month",
"monthCode",
"monthsInYear",
"toString",
"weekOfYear",
"year",
"yearOfWeek",
].forEach((methodName) => {
trackingMethods[methodName] = function (...args) {
calls.push(`call ${formatPropertyName(methodName, objectName)}`);
if (methodName in methodOverrides) {
Expand Down
28 changes: 26 additions & 2 deletions test/built-ins/Temporal/Calendar/from/calendar-object.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,33 @@

/*---
esid: sec-temporal.calendar.from
description: Converting a plain object to Temporal.Calendar gives the same object
description: >
Converting an object implementing the Calendar protocol to Temporal.Calendar
gives the same object
features: [Temporal]
---*/

const custom = { id: "custom-calendar" };
const custom = {
dateAdd() {},
dateFromFields() {},
dateUntil() {},
day() {},
dayOfWeek() {},
dayOfYear() {},
daysInMonth() {},
daysInWeek() {},
daysInYear() {},
fields() {},
id: "custom-calendar",
inLeapYear() {},
mergeFields() {},
month() {},
monthCode() {},
monthDayFromFields() {},
monthsInYear() {},
weekOfYear() {},
year() {},
yearMonthFromFields() {},
yearOfWeek() {},
};
assert.sameValue(Temporal.Calendar.from(custom), custom);
4 changes: 3 additions & 1 deletion test/built-ins/Temporal/Calendar/from/calendar-wrong-type.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ const rangeErrorTests = [
["", "empty string"],
[1, "number that doesn't convert to a valid ISO string"],
[1n, "bigint"],
[new Temporal.TimeZone("UTC"), "time zone instance"],
];

for (const [arg, description] of rangeErrorTests) {
Expand All @@ -24,6 +23,9 @@ for (const [arg, description] of rangeErrorTests) {

const typeErrorTests = [
[Symbol(), "symbol"],
[{}, "plain object that doesn't implement the protocol"],
[new Temporal.TimeZone("UTC"), "time zone instance"],
[Temporal.Calendar, "Temporal.Calendar, object"],
];

for (const [arg, description] of typeErrorTests) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ for (const [calendar, description] of rangeErrorTests) {

const typeErrorTests = [
[Symbol(), "symbol"],
[{}, "plain object"], // TypeError due to missing dateFromFields()
[Temporal.Calendar, "Temporal.Calendar, object"], // ditto
[{}, "plain object that doesn't implement the protocol"],
[new Temporal.TimeZone("UTC"), "time zone instance"],
[Temporal.Calendar, "Temporal.Calendar, object"],
[Temporal.Calendar.prototype, "Temporal.Calendar.prototype, object"], // fails brand check in dateFromFields()
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,29 @@ features: [Temporal]
---*/

const expected = [
// ToTemporalDate → GetTemporalCalendarWithISODefault
// ToTemporalDate → GetTemporalCalendarSlotValueWithISODefault
"get date.calendar",
"has date.calendar.dateAdd",
"has date.calendar.dateFromFields",
"has date.calendar.dateUntil",
"has date.calendar.day",
"has date.calendar.dayOfWeek",
"has date.calendar.dayOfYear",
"has date.calendar.daysInMonth",
"has date.calendar.daysInWeek",
"has date.calendar.daysInYear",
"has date.calendar.fields",
"has date.calendar.id",
"has date.calendar.inLeapYear",
"has date.calendar.mergeFields",
"has date.calendar.month",
"has date.calendar.monthCode",
"has date.calendar.monthDayFromFields",
"has date.calendar.monthsInYear",
"has date.calendar.weekOfYear",
"has date.calendar.year",
"has date.calendar.yearMonthFromFields",
"has date.calendar.yearOfWeek",
// ToTemporalDate → CalendarFields
"get date.calendar.fields",
"call date.calendar.fields",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ const rangeErrorTests = [
["", "empty string"],
[1, "number that doesn't convert to a valid ISO string"],
[1n, "bigint"],
[new Temporal.TimeZone("UTC"), "time zone instance"],
];

for (const [calendar, description] of rangeErrorTests) {
Expand All @@ -29,8 +28,9 @@ for (const [calendar, description] of rangeErrorTests) {

const typeErrorTests = [
[Symbol(), "symbol"],
[{}, "plain object"], // TypeError due to missing dateFromFields()
[Temporal.Calendar, "Temporal.Calendar, object"], // ditto
[{}, "plain object that doesn't implement the protocol"],
[new Temporal.TimeZone("UTC"), "time zone instance"],
[Temporal.Calendar, "Temporal.Calendar, object"],
[Temporal.Calendar.prototype, "Temporal.Calendar.prototype, object"], // fails brand check in dateFromFields()
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,29 @@ features: [Temporal]
---*/

const expected = [
// ToTemporalDate 1 → GetTemporalCalendarWithISODefault
// ToTemporalDate 1 → GetTemporalCalendarSlotValueWithISODefault
"get one.calendar",
"has one.calendar.dateAdd",
"has one.calendar.dateFromFields",
"has one.calendar.dateUntil",
"has one.calendar.day",
"has one.calendar.dayOfWeek",
"has one.calendar.dayOfYear",
"has one.calendar.daysInMonth",
"has one.calendar.daysInWeek",
"has one.calendar.daysInYear",
"has one.calendar.fields",
"has one.calendar.id",
"has one.calendar.inLeapYear",
"has one.calendar.mergeFields",
"has one.calendar.month",
"has one.calendar.monthCode",
"has one.calendar.monthDayFromFields",
"has one.calendar.monthsInYear",
"has one.calendar.weekOfYear",
"has one.calendar.year",
"has one.calendar.yearMonthFromFields",
"has one.calendar.yearOfWeek",
// ToTemporalDate 1 → CalendarFields
"get one.calendar.fields",
"call one.calendar.fields",
Expand All @@ -30,8 +51,29 @@ const expected = [
// ToTemporalDate 1 → CalendarDateFromFields
"get one.calendar.dateFromFields",
"call one.calendar.dateFromFields",
// ToTemporalDate 2 → GetTemporalCalendarWithISODefault
// ToTemporalDate 2 → GetTemporalCalendarSlotValueWithISODefault
"get two.calendar",
"has two.calendar.dateAdd",
"has two.calendar.dateFromFields",
"has two.calendar.dateUntil",
"has two.calendar.day",
"has two.calendar.dayOfWeek",
"has two.calendar.dayOfYear",
"has two.calendar.daysInMonth",
"has two.calendar.daysInWeek",
"has two.calendar.daysInYear",
"has two.calendar.fields",
"has two.calendar.id",
"has two.calendar.inLeapYear",
"has two.calendar.mergeFields",
"has two.calendar.month",
"has two.calendar.monthCode",
"has two.calendar.monthDayFromFields",
"has two.calendar.monthsInYear",
"has two.calendar.weekOfYear",
"has two.calendar.year",
"has two.calendar.yearMonthFromFields",
"has two.calendar.yearOfWeek",
// ToTemporalDate 2 → CalendarFields
"get two.calendar.fields",
"call two.calendar.fields",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ for (const [calendar, description] of rangeErrorTests) {

const typeErrorTests = [
[Symbol(), "symbol"],
[{}, "plain object"], // TypeError due to missing dateFromFields()
[Temporal.Calendar, "Temporal.Calendar, object"], // ditto
[{}, "plain object that doesn't implement the protocol"],
[new Temporal.TimeZone("UTC"), "time zone instance"],
[Temporal.Calendar, "Temporal.Calendar, object"],
[Temporal.Calendar.prototype, "Temporal.Calendar.prototype, object"], // fails brand check in dateFromFields()
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ for (const [calendar, description] of rangeErrorTests) {

const typeErrorTests = [
[Symbol(), "symbol"],
[{}, "plain object"], // TypeError due to missing dateFromFields()
[Temporal.Calendar, "Temporal.Calendar, object"], // ditto
[{}, "plain object that doesn't implement the protocol"],
[new Temporal.TimeZone("UTC"), "time zone instance"],
[Temporal.Calendar, "Temporal.Calendar, object"],
[Temporal.Calendar.prototype, "Temporal.Calendar.prototype, object"], // fails brand check in dateFromFields()
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ for (const [calendar, description] of rangeErrorTests) {

const typeErrorTests = [
[Symbol(), "symbol"],
[{}, "plain object"], // TypeError due to missing dateFromFields()
[Temporal.Calendar, "Temporal.Calendar, object"], // ditto
[{}, "plain object that doesn't implement the protocol"],
[new Temporal.TimeZone("UTC"), "time zone instance"],
[Temporal.Calendar, "Temporal.Calendar, object"],
[Temporal.Calendar.prototype, "Temporal.Calendar.prototype, object"], // fails brand check in dateFromFields()
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ for (const [calendar, description] of rangeErrorTests) {

const typeErrorTests = [
[Symbol(), "symbol"],
[{}, "plain object"], // TypeError due to missing dateFromFields()
[Temporal.Calendar, "Temporal.Calendar, object"], // ditto
[{}, "plain object that doesn't implement the protocol"],
[new Temporal.TimeZone("UTC"), "time zone instance"],
[Temporal.Calendar, "Temporal.Calendar, object"],
[Temporal.Calendar.prototype, "Temporal.Calendar.prototype, object"], // fails brand check in dateFromFields()
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ for (const [calendar, description] of rangeErrorTests) {

const typeErrorTests = [
[Symbol(), "symbol"],
[{}, "plain object"], // TypeError due to missing dateFromFields()
[Temporal.Calendar, "Temporal.Calendar, object"], // ditto
[{}, "plain object that doesn't implement the protocol"],
[new Temporal.TimeZone("UTC"), "time zone instance"],
[Temporal.Calendar, "Temporal.Calendar, object"],
[Temporal.Calendar.prototype, "Temporal.Calendar.prototype, object"], // fails brand check in dateFromFields()
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ for (const [calendar, description] of rangeErrorTests) {

const typeErrorTests = [
[Symbol(), "symbol"],
[{}, "plain object"], // TypeError due to missing dateFromFields()
[Temporal.Calendar, "Temporal.Calendar, object"], // ditto
[{}, "plain object that doesn't implement the protocol"],
[new Temporal.TimeZone("UTC"), "time zone instance"],
[Temporal.Calendar, "Temporal.Calendar, object"],
[Temporal.Calendar.prototype, "Temporal.Calendar.prototype, object"], // fails brand check in dateFromFields()
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ for (const [calendar, description] of rangeErrorTests) {

const typeErrorTests = [
[Symbol(), "symbol"],
[{}, "plain object"], // TypeError due to missing dateFromFields()
[Temporal.Calendar, "Temporal.Calendar, object"], // ditto
[{}, "plain object that doesn't implement the protocol"],
[new Temporal.TimeZone("UTC"), "time zone instance"],
[Temporal.Calendar, "Temporal.Calendar, object"],
[Temporal.Calendar.prototype, "Temporal.Calendar.prototype, object"], // fails brand check in dateFromFields()
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ for (const [calendar, description] of rangeErrorTests) {

const typeErrorTests = [
[Symbol(), "symbol"],
[{}, "plain object"], // TypeError due to missing dateFromFields()
[Temporal.Calendar, "Temporal.Calendar, object"], // ditto
[{}, "plain object that doesn't implement the protocol"],
[new Temporal.TimeZone("UTC"), "time zone instance"],
[Temporal.Calendar, "Temporal.Calendar, object"],
[Temporal.Calendar.prototype, "Temporal.Calendar.prototype, object"], // fails brand check in dateFromFields()
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ for (const [calendar, description] of rangeErrorTests) {

const typeErrorTests = [
[Symbol(), "symbol"],
[{}, "plain object"], // TypeError due to missing dateFromFields()
[Temporal.Calendar, "Temporal.Calendar, object"], // ditto
[{}, "plain object that doesn't implement the protocol"],
[new Temporal.TimeZone("UTC"), "time zone instance"],
[Temporal.Calendar, "Temporal.Calendar, object"],
[Temporal.Calendar.prototype, "Temporal.Calendar.prototype, object"], // fails brand check in dateFromFields()
];

Expand Down
Loading

0 comments on commit 80b37da

Please sign in to comment.