[ECMA-402] Implement Intl.Locale
authorross.kirsling@sony.com <ross.kirsling@sony.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 6 May 2020 06:01:07 +0000 (06:01 +0000)
committerross.kirsling@sony.com <ross.kirsling@sony.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 6 May 2020 06:01:07 +0000 (06:01 +0000)
https://bugs.webkit.org/show_bug.cgi?id=209772

Reviewed by Darin Adler and Saam Barati.

JSTests:

* stress/intl-locale.js: Added.
* stress/intl-locale-as-intl-param.js: Added.

* test262/config.yaml:
Enable Intl.Locale feature with flag.

* test262/expectations.yaml:
Mark known failures.
None of these should be specific to Intl.Locale.

Source/JavaScriptCore:

This patch implements the recent ECMA-402 feature Intl.Locale.

This is effectively a wrapper class for all the pieces of uloc.h that ECMA-402 cares about.
(If we used the C++ API, there's a LocaleBuilder that would make this much easier, but in sticking to the C API,
it's basically an object that has an ICU localeID as data and uloc_* functions as methods / getters.
Furthermore, there's no way to modify said data, so every method / getter can be lazy and cache its result.)

Usage example:
  >>> locale = new Intl.Locale('ja', { region: 'JP', calendar: 'japanese', numeric: false })
  "ja-JP-u-ca-japanese-kn-false"
  >>> locale.baseName
  "ja-JP"

Intl.Locale can be used anywhere that Intl APIs accept locale strings as input parameters,
and is moreover hoped to be the class by which future Web APIs will handle the current locale.

This feature is runtime-guarded by the `useIntlLocale` option.

* CMakeLists.txt:
* DerivedSources-input.xcfilelist:
* DerivedSources-output.xcfilelist:
* DerivedSources.make:
* JavaScriptCore.xcodeproj/project.pbxproj:
* Sources.txt:
* runtime/CommonIdentifiers.h:
* runtime/IntlLocale.cpp: Added.
* runtime/IntlLocale.h: Added.
* runtime/IntlLocaleConstructor.cpp: Added.
* runtime/IntlLocaleConstructor.h: Added.
* runtime/IntlLocalePrototype.cpp: Added.
* runtime/IntlLocalePrototype.h: Added.
* runtime/IntlObject.cpp:
(JSC::IntlObject::finishCreation):
(JSC::localeIDBufferForLanguageTag): Added.
(JSC::languageTagForLocaleID): Renamed from JSC::convertICULocaleToBCP47LanguageTag.
(JSC::intlAvailableLocales):
(JSC::intlCollatorAvailableLocales):
(JSC::canonicalizeLanguageTag):
(JSC::canonicalizeLocaleList):
(JSC::defaultLocale):
* runtime/IntlObject.h:
* runtime/JSGlobalObject.cpp:
(JSC::JSGlobalObject::init):
(JSC::JSGlobalObject::visitChildren):
* runtime/JSGlobalObject.h:
(JSC::JSGlobalObject::collatorStructure):
(JSC::JSGlobalObject::numberFormatStructure):
(JSC::JSGlobalObject::localeStructure):
* runtime/OptionsList.h:
* runtime/VM.cpp:
(JSC::VM::VM):
* runtime/VM.h:

Tools:

* Scripts/run-jsc-stress-tests:
Add runIntlLocaleEnabled.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@261215 268f45cc-cd09-0410-ab3c-d52691b4dbfc

28 files changed:
JSTests/ChangeLog
JSTests/stress/intl-locale-as-intl-param.js [new file with mode: 0644]
JSTests/stress/intl-locale.js [new file with mode: 0644]
JSTests/test262/config.yaml
JSTests/test262/expectations.yaml
Source/JavaScriptCore/CMakeLists.txt
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/DerivedSources-input.xcfilelist
Source/JavaScriptCore/DerivedSources-output.xcfilelist
Source/JavaScriptCore/DerivedSources.make
Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj
Source/JavaScriptCore/Sources.txt
Source/JavaScriptCore/runtime/CommonIdentifiers.h
Source/JavaScriptCore/runtime/IntlLocale.cpp [new file with mode: 0644]
Source/JavaScriptCore/runtime/IntlLocale.h [new file with mode: 0644]
Source/JavaScriptCore/runtime/IntlLocaleConstructor.cpp [new file with mode: 0644]
Source/JavaScriptCore/runtime/IntlLocaleConstructor.h [new file with mode: 0644]
Source/JavaScriptCore/runtime/IntlLocalePrototype.cpp [new file with mode: 0644]
Source/JavaScriptCore/runtime/IntlLocalePrototype.h [new file with mode: 0644]
Source/JavaScriptCore/runtime/IntlObject.cpp
Source/JavaScriptCore/runtime/IntlObject.h
Source/JavaScriptCore/runtime/JSGlobalObject.cpp
Source/JavaScriptCore/runtime/JSGlobalObject.h
Source/JavaScriptCore/runtime/OptionsList.h
Source/JavaScriptCore/runtime/VM.cpp
Source/JavaScriptCore/runtime/VM.h
Tools/ChangeLog
Tools/Scripts/run-jsc-stress-tests

index 7bd0ee8..3e4e3e9 100644 (file)
@@ -1,3 +1,20 @@
+2020-05-05  Ross Kirsling  <ross.kirsling@sony.com>
+
+        [ECMA-402] Implement Intl.Locale
+        https://bugs.webkit.org/show_bug.cgi?id=209772
+
+        Reviewed by Darin Adler and Saam Barati.
+
+        * stress/intl-locale.js: Added.
+        * stress/intl-locale-as-intl-param.js: Added.
+
+        * test262/config.yaml:
+        Enable Intl.Locale feature with flag.
+
+        * test262/expectations.yaml:
+        Mark known failures.
+        None of these should be specific to Intl.Locale.
+
 2020-05-05  Yusuke Suzuki  <ysuzuki@apple.com>
 
         [JSC] Implement BigInt.asIntN and BigInt.asUintN
diff --git a/JSTests/stress/intl-locale-as-intl-param.js b/JSTests/stress/intl-locale-as-intl-param.js
new file mode 100644 (file)
index 0000000..0bdfb76
--- /dev/null
@@ -0,0 +1,45 @@
+//@ runIntlLocaleEnabled("--useIntlRelativeTimeFormat=true")
+
+const tag = 'ja-Hant-KR-u-ca-buddhist-co-eor-hc-h11-kf-lower-kn-false-nu-thai';
+const locale = new Intl.Locale(tag);
+
+function shouldBeSameWithTagOrLocale(func) {
+    const actual = func(locale);
+    const expected = func(tag);
+    if (actual !== expected)
+        throw new Error(`expected ${expected} but got ${actual}`);
+}
+
+shouldBeSameWithTagOrLocale(tagOrLocale => tagOrLocale.toString());
+shouldBeSameWithTagOrLocale(tagOrLocale => new Intl.Locale(tagOrLocale).toString());
+
+shouldBeSameWithTagOrLocale(tagOrLocale => JSON.stringify(Intl.getCanonicalLocales(tagOrLocale)));
+shouldBeSameWithTagOrLocale(tagOrLocale => JSON.stringify(Intl.getCanonicalLocales(['en', tagOrLocale, 'fr'])));
+
+shouldBeSameWithTagOrLocale(tagOrLocale => JSON.stringify(Intl.Collator.supportedLocalesOf(tagOrLocale)));
+shouldBeSameWithTagOrLocale(tagOrLocale => JSON.stringify(Intl.Collator.supportedLocalesOf(['en', tagOrLocale, 'fr'])));
+shouldBeSameWithTagOrLocale(tagOrLocale => new Intl.Collator(tagOrLocale).resolvedOptions().locale);
+
+shouldBeSameWithTagOrLocale(tagOrLocale => JSON.stringify(Intl.DateTimeFormat.supportedLocalesOf(tagOrLocale)));
+shouldBeSameWithTagOrLocale(tagOrLocale => JSON.stringify(Intl.DateTimeFormat.supportedLocalesOf(['en', tagOrLocale, 'fr'])));
+shouldBeSameWithTagOrLocale(tagOrLocale => new Intl.DateTimeFormat(tagOrLocale).resolvedOptions().locale);
+
+shouldBeSameWithTagOrLocale(tagOrLocale => JSON.stringify(Intl.NumberFormat.supportedLocalesOf(tagOrLocale)));
+shouldBeSameWithTagOrLocale(tagOrLocale => JSON.stringify(Intl.NumberFormat.supportedLocalesOf(['en', tagOrLocale, 'fr'])));
+shouldBeSameWithTagOrLocale(tagOrLocale => new Intl.NumberFormat(tagOrLocale).resolvedOptions().locale);
+
+shouldBeSameWithTagOrLocale(tagOrLocale => JSON.stringify(Intl.PluralRules.supportedLocalesOf(tagOrLocale)));
+shouldBeSameWithTagOrLocale(tagOrLocale => JSON.stringify(Intl.PluralRules.supportedLocalesOf(['en', tagOrLocale, 'fr'])));
+shouldBeSameWithTagOrLocale(tagOrLocale => new Intl.PluralRules(tagOrLocale).resolvedOptions().locale);
+
+shouldBeSameWithTagOrLocale(tagOrLocale => JSON.stringify(Intl.RelativeTimeFormat.supportedLocalesOf(tagOrLocale)));
+shouldBeSameWithTagOrLocale(tagOrLocale => JSON.stringify(Intl.RelativeTimeFormat.supportedLocalesOf(['en', tagOrLocale, 'fr'])));
+shouldBeSameWithTagOrLocale(tagOrLocale => new Intl.RelativeTimeFormat(tagOrLocale).resolvedOptions().locale);
+
+shouldBeSameWithTagOrLocale(tagOrLocale => [3].toLocaleString(tagOrLocale));
+shouldBeSameWithTagOrLocale(tagOrLocale => 3n.toLocaleString(tagOrLocale));
+shouldBeSameWithTagOrLocale(tagOrLocale => new Date(0).toLocaleString(tagOrLocale, { timeZone: 'UTC' }));
+shouldBeSameWithTagOrLocale(tagOrLocale => 3..toLocaleString(tagOrLocale));
+shouldBeSameWithTagOrLocale(tagOrLocale => 'ä'.localeCompare('a', tagOrLocale));
+shouldBeSameWithTagOrLocale(tagOrLocale => 'Æ'.toLocaleLowerCase(tagOrLocale));
+shouldBeSameWithTagOrLocale(tagOrLocale => 'æ'.toLocaleUpperCase(tagOrLocale));
diff --git a/JSTests/stress/intl-locale.js b/JSTests/stress/intl-locale.js
new file mode 100644 (file)
index 0000000..6763599
--- /dev/null
@@ -0,0 +1,226 @@
+//@ runIntlLocaleEnabled
+
+function shouldBe(actual, expected) {
+    if (actual !== expected)
+        throw new Error(`expected ${expected} but got ${actual}`);
+}
+
+function shouldNotThrow(func) {
+    func();
+}
+
+function shouldThrow(func, errorType) {
+    let error;
+    try {
+        func();
+    } catch (e) {
+        error = e;
+    }
+
+    if (!(error instanceof errorType))
+        throw new Error(`Expected ${errorType.name}!`);
+}
+
+shouldBe(Intl.Locale instanceof Function, true);
+shouldBe(Intl.Locale.length, 1);
+shouldBe(Object.getOwnPropertyDescriptor(Intl.Locale, 'prototype').writable, false);
+shouldBe(Object.getOwnPropertyDescriptor(Intl.Locale, 'prototype').enumerable, false);
+shouldBe(Object.getOwnPropertyDescriptor(Intl.Locale, 'prototype').configurable, false);
+
+shouldThrow(() => Intl.Locale(), TypeError);
+shouldThrow(() => Intl.Locale.call({}), TypeError);
+
+shouldThrow(() => new Intl.Locale(), TypeError);
+shouldThrow(() => new Intl.Locale(5), TypeError);
+shouldThrow(() => new Intl.Locale(''), RangeError);
+shouldThrow(() => new Intl.Locale('a'), RangeError);
+shouldThrow(() => new Intl.Locale('abcdefghij'), RangeError);
+shouldThrow(() => new Intl.Locale('#$'), RangeError);
+shouldThrow(() => new Intl.Locale('en-@-abc'), RangeError);
+shouldThrow(() => new Intl.Locale('en-u'), RangeError);
+shouldThrow(() => new Intl.Locale('en-u-kn-true-u-ko-true'), RangeError);
+shouldThrow(() => new Intl.Locale('en-x'), RangeError);
+shouldThrow(() => new Intl.Locale('en-*'), RangeError);
+shouldThrow(() => new Intl.Locale('en-'), RangeError);
+shouldThrow(() => new Intl.Locale('en--US'), RangeError);
+shouldThrow(() => new Intl.Locale(['en', 'ja']), RangeError);
+shouldThrow(() => new Intl.Locale({}), RangeError);
+shouldThrow(() => new Intl.Locale({ toString() { throw new Error(); } }), Error);
+shouldNotThrow(() => new Intl.Locale({ toString() { return 'en'; } }));
+
+shouldThrow(() => new Intl.Locale('en', null), TypeError);
+
+shouldBe(new Intl.Locale('en') instanceof Intl.Locale, true);
+
+{
+    class DerivedLocale extends Intl.Locale {}
+
+    const dl = new DerivedLocale('en');
+    shouldBe(dl instanceof DerivedLocale, true);
+    shouldBe(dl instanceof Intl.Locale, true);
+    shouldBe(dl.maximize, Intl.Locale.prototype.maximize);
+    shouldBe(Object.getPrototypeOf(dl), DerivedLocale.prototype);
+    shouldBe(Object.getPrototypeOf(DerivedLocale.prototype), Intl.Locale.prototype);
+}
+
+const validLanguageTags = [
+    'de', // ISO 639 language code
+    'de-DE', // + ISO 3166-1 country code
+    'DE-de', // tags are case-insensitive
+    'cmn', // ISO 639 language code
+    'cmn-Hans', // + script code
+    'CMN-hANS', // tags are case-insensitive
+    'cmn-hans-cn', // + ISO 3166-1 country code
+    'es-419', // + UN M.49 region code
+    'es-419-u-nu-latn-cu-bob', // + Unicode locale extension sequence
+    'i-klingon', // grandfathered tag
+    'cmn-hans-cn-t-ca-u-ca-x-t-u', // singleton subtags can also be used as private use subtags
+    'enochian-enochian', // language and variant subtags may be the same
+    'de-gregory-u-ca-gregory', // variant and extension subtags may be the same
+    'aa-a-foo-x-a-foo-bar', // variant subtags can also be used as private use subtags
+    'x-en-US-12345', // anything goes in private use tags
+    'x-12345-12345-en-US',
+    'x-en-US-12345-12345',
+    'x-en-u-foo',
+    'x-en-u-foo-u-bar'
+];
+for (let validLanguageTag of validLanguageTags)
+    shouldNotThrow(() => new Intl.Locale(validLanguageTag));
+
+shouldBe(Object.getPrototypeOf(Intl.Locale.prototype), Object.prototype);
+
+shouldBe(Intl.Locale.prototype.constructor, Intl.Locale);
+
+shouldBe(Intl.Locale.prototype[Symbol.toStringTag], 'Intl.Locale');
+shouldBe(Object.prototype.toString.call(Intl.Locale.prototype), '[object Intl.Locale]');
+shouldBe(Object.getOwnPropertyDescriptor(Intl.Locale.prototype, Symbol.toStringTag).writable, false);
+shouldBe(Object.getOwnPropertyDescriptor(Intl.Locale.prototype, Symbol.toStringTag).enumerable, false);
+shouldBe(Object.getOwnPropertyDescriptor(Intl.Locale.prototype, Symbol.toStringTag).configurable, true);
+
+shouldBe(Intl.Locale.prototype.maximize.length, 0);
+shouldBe(Intl.Locale.prototype.minimize.length, 0);
+shouldBe(Intl.Locale.prototype.toString.length, 0);
+shouldThrow(() => Intl.Locale.prototype.maximize.call({ toString() { return 'en'; } }), TypeError);
+shouldThrow(() => Intl.Locale.prototype.minimize.call({ toString() { return 'en'; } }), TypeError);
+shouldThrow(() => Intl.Locale.prototype.toString.call({ toString() { return 'en'; } }), TypeError);
+shouldThrow(() => Intl.Locale.prototype.baseName, TypeError);
+shouldThrow(() => Intl.Locale.prototype.calendar, TypeError);
+shouldThrow(() => Intl.Locale.prototype.caseFirst, TypeError);
+shouldThrow(() => Intl.Locale.prototype.collation, TypeError);
+shouldThrow(() => Intl.Locale.prototype.hourCycle, TypeError);
+shouldThrow(() => Intl.Locale.prototype.numeric, TypeError);
+shouldThrow(() => Intl.Locale.prototype.numberingSystem, TypeError);
+shouldThrow(() => Intl.Locale.prototype.language, TypeError);
+shouldThrow(() => Intl.Locale.prototype.script, TypeError);
+shouldThrow(() => Intl.Locale.prototype.region, TypeError);
+
+shouldBe(new Intl.Locale('en').maximize(), 'en-Latn-US');
+shouldBe(new Intl.Locale('en-Latn-US').maximize(), 'en-Latn-US');
+shouldBe(new Intl.Locale('en-u-nu-thai-x-foo').maximize(), 'en-Latn-US-u-nu-thai-x-foo');
+shouldBe(new Intl.Locale('ja').maximize(), 'ja-Jpan-JP');
+shouldBe(new Intl.Locale('zh').maximize(), 'zh-Hans-CN');
+shouldBe(new Intl.Locale('zh-Hant').maximize(), 'zh-Hant-TW');
+shouldBe(new Intl.Locale('zh', { script: 'Hant' }).maximize(), 'zh-Hant-TW');
+
+shouldBe(new Intl.Locale('en').minimize(), 'en');
+shouldBe(new Intl.Locale('en-Latn-US').minimize(), 'en');
+shouldBe(new Intl.Locale('en-Latn-US-u-nu-thai-x-foo').minimize(), 'en-u-nu-thai-x-foo');
+shouldBe(new Intl.Locale('ja-Jpan-JP').minimize(), 'ja');
+shouldBe(new Intl.Locale('zh-Hans-CN').minimize(), 'zh');
+shouldBe(new Intl.Locale('zh-Hant-TW').minimize(), 'zh-TW');
+shouldBe(new Intl.Locale('zh', { script: 'Hant', region: 'TW' }).minimize(), 'zh-TW');
+
+shouldBe(new Intl.Locale('en').toString(), 'en');
+shouldBe(new Intl.Locale('En-laTn-us-variAnt-fOObar-1abc-U-kn-tRue-A-aa-aaa-x-RESERVED').toString(), 'en-Latn-US-variant-foobar-1abc-a-aa-aaa-u-kn-true-x-reserved');
+shouldBe(new Intl.Locale('cel-gaulish', { script: 'Arab', numberingSystem: 'gujr' }).toString(), 'xtg-Arab-u-nu-gujr-x-cel-gaulish');
+shouldBe(new Intl.Locale('en-Latn-US-u-ca-gregory-co-phonebk-hc-h12-kf-upper-kn-nu-latn').toString(), 'en-Latn-US-u-ca-gregory-co-phonebk-hc-h12-kf-upper-kn-true-nu-latn');
+
+const options = {
+    calendar: 'buddhist',
+    caseFirst: 'lower',
+    collation: 'eor',
+    hourCycle: 'h11',
+    numeric: false,
+    numberingSystem: 'thai',
+    language: 'ja',
+    script: 'Hant',
+    region: 'KR'
+};
+const expected = 'ja-Hant-KR-u-ca-buddhist-co-eor-hc-h11-kf-lower-kn-false-nu-thai';
+shouldBe(new Intl.Locale('en', options).toString(), expected);
+shouldBe(new Intl.Locale('en-Latn-US-u-ca-gregory-co-phonebk-hc-h12-kf-upper-kn-nu-latn', options).toString(), expected);
+
+shouldBe(new Intl.Locale('en').baseName, 'en');
+shouldBe(new Intl.Locale('en-Latn').baseName, 'en-Latn');
+shouldBe(new Intl.Locale('en-US').baseName, 'en-US');
+shouldBe(new Intl.Locale('en-Latn-US').baseName, 'en-Latn-US');
+shouldBe(new Intl.Locale('en-variant-u-kn').baseName, 'en-variant');
+shouldBe(new Intl.Locale('en-Latn-variant-u-kn').baseName, 'en-Latn-variant');
+shouldBe(new Intl.Locale('en-variant-u-kn', { script: 'Latn' }).baseName, 'en-Latn-variant');
+shouldBe(new Intl.Locale('en-US-variant-u-kn').baseName, 'en-US-variant');
+shouldBe(new Intl.Locale('en-variant-u-kn', { region: 'US' }).baseName, 'en-US-variant');
+shouldBe(new Intl.Locale('en-Latn-US-variant-u-kn').baseName, 'en-Latn-US-variant');
+shouldBe(new Intl.Locale('en-variant-u-kn', { script: 'Latn', region: 'US' }).baseName, 'en-Latn-US-variant');
+
+shouldBe(new Intl.Locale('en').calendar, undefined);
+shouldBe(new Intl.Locale('en-u-ca-japanese').calendar, 'japanese');
+shouldBe(new Intl.Locale('en', { calendar: 'japanese' }).calendar, 'japanese');
+shouldBe(new Intl.Locale('en-u-ca-japanese', { calendar: 'dangi' }).calendar, 'dangi');
+shouldBe(new Intl.Locale('en-u-ca-islamicc').calendar, 'islamic-civil');
+shouldBe(new Intl.Locale('en', { calendar: 'islamicc' }).calendar, 'islamic-civil');
+shouldThrow(() => new Intl.Locale('en', { calendar: { toString() { throw new Error(); } } }), Error);
+shouldThrow(() => new Intl.Locale('en', { calendar: 'ng' }), RangeError);
+
+shouldBe(new Intl.Locale('en').collation, undefined);
+shouldBe(new Intl.Locale('en-u-co-phonebk').collation, 'phonebk');
+shouldBe(new Intl.Locale('en', { collation: 'phonebk' }).collation, 'phonebk');
+shouldBe(new Intl.Locale('en-u-co-phonebk', { collation: 'eor' }).collation, 'eor');
+shouldThrow(() => new Intl.Locale('en', { collation: { toString() { throw new Error(); } } }), Error);
+shouldThrow(() => new Intl.Locale('en', { collation: 'ng' }), RangeError);
+
+shouldBe(new Intl.Locale('en').caseFirst, undefined);
+shouldBe(new Intl.Locale('en-u-kf-upper').caseFirst, 'upper');
+shouldBe(new Intl.Locale('en', { caseFirst: 'upper' }).caseFirst, 'upper');
+shouldBe(new Intl.Locale('en-u-kf-upper', { caseFirst: 'lower' }).caseFirst, 'lower');
+shouldThrow(() => new Intl.Locale('en', { caseFirst: { toString() { throw new Error(); } } }), Error);
+shouldThrow(() => new Intl.Locale('en', { caseFirst: 'bad' }), RangeError);
+
+shouldBe(new Intl.Locale('en').numeric, false);
+shouldBe(new Intl.Locale('en-u-kn').numeric, true);
+shouldBe(new Intl.Locale('en-u-kn-true').numeric, true);
+shouldBe(new Intl.Locale('en', { numeric: true }).numeric, true);
+shouldBe(new Intl.Locale('en-u-kn', { numeric: false }).numeric, false);
+shouldBe(new Intl.Locale('en-u-kn-true', { numeric: false }).numeric, false);
+shouldThrow(() => new Intl.Locale('en', { get numeric() { throw new Error(); } }), Error);
+
+shouldBe(new Intl.Locale('en').hourCycle, undefined);
+shouldBe(new Intl.Locale('en-u-hc-h11').hourCycle, 'h11');
+shouldBe(new Intl.Locale('en', { hourCycle: 'h11' }).hourCycle, 'h11');
+shouldBe(new Intl.Locale('en-u-hc-h11', { hourCycle: 'h23' }).hourCycle, 'h23');
+shouldThrow(() => new Intl.Locale('en', { hourCycle: { toString() { throw new Error(); } } }), Error);
+shouldThrow(() => new Intl.Locale('en', { hourCycle: 'bad' }), RangeError);
+
+shouldBe(new Intl.Locale('en').numberingSystem, undefined);
+shouldBe(new Intl.Locale('en-u-nu-hanidec').numberingSystem, 'hanidec');
+shouldBe(new Intl.Locale('en', { numberingSystem: 'hanidec' }).numberingSystem, 'hanidec');
+shouldBe(new Intl.Locale('en-u-nu-hanidec', { numberingSystem: 'gujr' }).numberingSystem, 'gujr');
+shouldThrow(() => new Intl.Locale('en', { numberingSystem: { toString() { throw new Error(); } } }), Error);
+shouldThrow(() => new Intl.Locale('en', { numberingSystem: 'ng' }), RangeError);
+
+shouldBe(new Intl.Locale('en-Latn-US').language, 'en');
+shouldBe(new Intl.Locale('en', { language: 'ar' }).language, 'ar');
+shouldBe(new Intl.Locale('cel-gaulish').language, 'xtg');
+shouldThrow(() => new Intl.Locale('en', { language: { toString() { throw new Error(); } } }), Error);
+shouldThrow(() => new Intl.Locale('en', { language: 'fail' }), RangeError);
+
+shouldBe(new Intl.Locale('en-Latn-US').script, 'Latn');
+shouldBe(new Intl.Locale('en-US').script, undefined);
+shouldBe(new Intl.Locale('en-Latn', { script: 'Kore' }).script, 'Kore');
+shouldThrow(() => new Intl.Locale('en', { script: { toString() { throw new Error(); } } }), Error);
+shouldThrow(() => new Intl.Locale('en', { script: 'bad' }), RangeError);
+
+shouldBe(new Intl.Locale('en-Latn-US').region, 'US');
+shouldBe(new Intl.Locale('en-Latn').region, undefined);
+shouldBe(new Intl.Locale('en-US', { region: 'KR' }).region, 'KR');
+shouldThrow(() => new Intl.Locale('en', { region: { toString() { throw new Error(); } } }), Error);
+shouldThrow(() => new Intl.Locale('en', { region: 'fail' }), RangeError);
index 3e38ce1..3691822 100644 (file)
@@ -2,6 +2,7 @@
 ---
 flags:
   BigInt: useBigInt
+  Intl.Locale: useIntlLocale
   Intl.RelativeTimeFormat: useIntlRelativeTimeFormat
   WeakRef: useWeakRefs
   class-fields-public: usePublicClassFields
@@ -30,7 +31,6 @@ skip:
     - Intl.DateTimeFormat-fractionalSecondDigits
     - Intl.DisplayNames
     - Intl.ListFormat
-    - Intl.Locale
     - Intl.NumberFormat-unified
     - Intl.Segmenter
   paths:
index 47f4df5..2635941 100644 (file)
@@ -1716,6 +1716,57 @@ test/intl402/Intl/getCanonicalLocales/unicode-ext-canonicalize-yes-to-true.js:
 test/intl402/Intl/getCanonicalLocales/unicode-ext-key-with-digit.js:
   default: 'Test262Error: Expected a RangeError to be thrown but no exception was thrown at all'
   strict mode: 'Test262Error: Expected a RangeError to be thrown but no exception was thrown at all'
+test/intl402/Locale/constructor-apply-options-canonicalizes-twice.js:
+  default: 'Test262Error: Expected SameValue(«ru-Armn-SU», «ru-Armn-AM») to be true'
+  strict mode: 'Test262Error: Expected SameValue(«ru-Armn-SU», «ru-Armn-AM») to be true'
+test/intl402/Locale/constructor-non-iana-canon.js:
+  default: 'Test262Error: new Intl.Locale("mo").maximize().toString() returns "ro-Latn-RO" Expected SameValue(«ro», «ro-Latn-RO») to be true'
+  strict mode: 'Test262Error: new Intl.Locale("mo").maximize().toString() returns "ro-Latn-RO" Expected SameValue(«ro», «ro-Latn-RO») to be true'
+test/intl402/Locale/constructor-options-language-valid-undefined.js:
+  default: 'Test262Error: Expected a RangeError to be thrown but no exception was thrown at all'
+  strict mode: 'Test262Error: Expected a RangeError to be thrown but no exception was thrown at all'
+test/intl402/Locale/constructor-options-language-valid.js:
+  default: 'Test262Error: Expected a RangeError to be thrown but no exception was thrown at all'
+  strict mode: 'Test262Error: Expected a RangeError to be thrown but no exception was thrown at all'
+test/intl402/Locale/constructor-options-numeric-undefined.js:
+  default: 'Test262Error: new Intl.Locale("en-u-kn-true", {numeric: undefined}).toString() returns "en-u-kn" Expected SameValue(«en-u-kn-true», «en-u-kn») to be true'
+  strict mode: 'Test262Error: new Intl.Locale("en-u-kn-true", {numeric: undefined}).toString() returns "en-u-kn" Expected SameValue(«en-u-kn-true», «en-u-kn») to be true'
+test/intl402/Locale/constructor-options-numeric-valid.js:
+  default: 'Test262Error: new Intl.Locale("en", {numeric: true}).toString() returns "true" Expected SameValue(«en-u-kn-true», «en-u-kn») to be true'
+  strict mode: 'Test262Error: new Intl.Locale("en", {numeric: true}).toString() returns "true" Expected SameValue(«en-u-kn-true», «en-u-kn») to be true'
+test/intl402/Locale/constructor-options-region-valid.js:
+  default: "Test262Error: new Intl.Locale('en', {region: \"554\"}).toString() returns \"en-NZ\" Expected SameValue(«en-554», «en-NZ») to be true"
+  strict mode: "Test262Error: new Intl.Locale('en', {region: \"554\"}).toString() returns \"en-NZ\" Expected SameValue(«en-554», «en-NZ») to be true"
+test/intl402/Locale/constructor-parse-twice.js:
+  default: 'Test262Error: Expected a RangeError to be thrown but no exception was thrown at all'
+  strict mode: 'Test262Error: Expected a RangeError to be thrown but no exception was thrown at all'
+test/intl402/Locale/constructor-tag.js:
+  default: 'Test262Error: new Intl.Locale("sl-ROZAJ-BISKE-1994").toString() returns "sl-1994-biske-rozaj" Expected SameValue(«sl-rozaj-biske-1994», «sl-1994-biske-rozaj») to be true'
+  strict mode: 'Test262Error: new Intl.Locale("sl-ROZAJ-BISKE-1994").toString() returns "sl-1994-biske-rozaj" Expected SameValue(«sl-rozaj-biske-1994», «sl-1994-biske-rozaj») to be true'
+test/intl402/Locale/extensions-grandfathered.js:
+  default: 'Test262Error: Expected a RangeError to be thrown but no exception was thrown at all'
+  strict mode: 'Test262Error: Expected a RangeError to be thrown but no exception was thrown at all'
+test/intl402/Locale/extensions-private.js:
+  default: 'Test262Error: Expected a RangeError to be thrown but no exception was thrown at all'
+  strict mode: 'Test262Error: Expected a RangeError to be thrown but no exception was thrown at all'
+test/intl402/Locale/getters-grandfathered.js:
+  default: 'Test262Error: Expected a RangeError to be thrown but no exception was thrown at all'
+  strict mode: 'Test262Error: Expected a RangeError to be thrown but no exception was thrown at all'
+test/intl402/Locale/getters.js:
+  default: 'Test262Error: Expected SameValue(«de-Latn-DE-u-ca-gregory-co-phonebk-hc-h23-kf-true-kn-false-nu-latn», «de-Latn-DE-u-ca-gregory-co-phonebk-hc-h23-kf-kn-false-nu-latn») to be true'
+  strict mode: 'Test262Error: Expected SameValue(«de-Latn-DE-u-ca-gregory-co-phonebk-hc-h23-kf-true-kn-false-nu-latn», «de-Latn-DE-u-ca-gregory-co-phonebk-hc-h23-kf-kn-false-nu-latn») to be true'
+test/intl402/Locale/invalid-tag-throws.js:
+  default: 'Test262Error: no-nyn is an invalid tag value Expected a RangeError to be thrown but no exception was thrown at all'
+  strict mode: 'Test262Error: no-nyn is an invalid tag value Expected a RangeError to be thrown but no exception was thrown at all'
+test/intl402/Locale/likely-subtags-grandfathered.js:
+  default: 'Test262Error: Expected a RangeError to be thrown but no exception was thrown at all'
+  strict mode: 'Test262Error: Expected a RangeError to be thrown but no exception was thrown at all'
+test/intl402/Locale/likely-subtags.js:
+  default: 'Test262Error: "en-u-co".maximize() should be "en-Latn-US-u-co" Expected SameValue(«en-Latn-US-u-co-yes», «en-Latn-US-u-co») to be true'
+  strict mode: 'Test262Error: "en-u-co".maximize() should be "en-Latn-US-u-co" Expected SameValue(«en-Latn-US-u-co-yes», «en-Latn-US-u-co») to be true'
+test/intl402/Locale/prototype/minimize/removing-likely-subtags-first-adds-likely-subtags.js:
+  default: 'Test262Error: "und".minimize() should be "en" Expected SameValue(«en-u-va-posix», «en») to be true'
+  strict mode: 'Test262Error: "und".minimize() should be "en" Expected SameValue(«en-u-va-posix», «en») to be true'
 test/intl402/RelativeTimeFormat/constructor/constructor/locales-valid.js:
   default: 'Test262Error: Grandfathered Expected a RangeError to be thrown but no exception was thrown at all'
   strict mode: 'Test262Error: Grandfathered Expected a RangeError to be thrown but no exception was thrown at all'
index bb4ea11..0d1fe30 100644 (file)
@@ -70,6 +70,7 @@ set(JavaScriptCore_OBJECT_LUT_SOURCES
     runtime/IntlCollatorPrototype.cpp
     runtime/IntlDateTimeFormatConstructor.cpp
     runtime/IntlDateTimeFormatPrototype.cpp
+    runtime/IntlLocalePrototype.cpp
     runtime/IntlNumberFormatConstructor.cpp
     runtime/IntlNumberFormatPrototype.cpp
     runtime/IntlObject.cpp
index e619a16..8668c76 100644 (file)
@@ -1,3 +1,63 @@
+2020-05-05  Ross Kirsling  <ross.kirsling@sony.com>
+
+        [ECMA-402] Implement Intl.Locale
+        https://bugs.webkit.org/show_bug.cgi?id=209772
+
+        Reviewed by Darin Adler and Saam Barati.
+
+        This patch implements the recent ECMA-402 feature Intl.Locale.
+
+        This is effectively a wrapper class for all the pieces of uloc.h that ECMA-402 cares about.
+        (If we used the C++ API, there's a LocaleBuilder that would make this much easier, but in sticking to the C API,
+        it's basically an object that has an ICU localeID as data and uloc_* functions as methods / getters.
+        Furthermore, there's no way to modify said data, so every method / getter can be lazy and cache its result.)
+
+        Usage example:
+          >>> locale = new Intl.Locale('ja', { region: 'JP', calendar: 'japanese', numeric: false })
+          "ja-JP-u-ca-japanese-kn-false"
+          >>> locale.baseName
+          "ja-JP"
+
+        Intl.Locale can be used anywhere that Intl APIs accept locale strings as input parameters,
+        and is moreover hoped to be the class by which future Web APIs will handle the current locale.
+
+        This feature is runtime-guarded by the `useIntlLocale` option.
+
+        * CMakeLists.txt:
+        * DerivedSources-input.xcfilelist:
+        * DerivedSources-output.xcfilelist:
+        * DerivedSources.make:
+        * JavaScriptCore.xcodeproj/project.pbxproj:
+        * Sources.txt:
+        * runtime/CommonIdentifiers.h:
+        * runtime/IntlLocale.cpp: Added.
+        * runtime/IntlLocale.h: Added.
+        * runtime/IntlLocaleConstructor.cpp: Added.
+        * runtime/IntlLocaleConstructor.h: Added.
+        * runtime/IntlLocalePrototype.cpp: Added.
+        * runtime/IntlLocalePrototype.h: Added.
+        * runtime/IntlObject.cpp:
+        (JSC::IntlObject::finishCreation):
+        (JSC::localeIDBufferForLanguageTag): Added.
+        (JSC::languageTagForLocaleID): Renamed from JSC::convertICULocaleToBCP47LanguageTag.
+        (JSC::intlAvailableLocales):
+        (JSC::intlCollatorAvailableLocales):
+        (JSC::canonicalizeLanguageTag):
+        (JSC::canonicalizeLocaleList):
+        (JSC::defaultLocale):
+        * runtime/IntlObject.h:
+        * runtime/JSGlobalObject.cpp:
+        (JSC::JSGlobalObject::init):
+        (JSC::JSGlobalObject::visitChildren):
+        * runtime/JSGlobalObject.h:
+        (JSC::JSGlobalObject::collatorStructure):
+        (JSC::JSGlobalObject::numberFormatStructure):
+        (JSC::JSGlobalObject::localeStructure):
+        * runtime/OptionsList.h:
+        * runtime/VM.cpp:
+        (JSC::VM::VM):
+        * runtime/VM.h:
+
 2020-05-05  Keith Miller  <keith_miller@apple.com>
 
         clobberize validator should use branchTest8 directly.
index f351044..fea4045 100644 (file)
@@ -136,6 +136,7 @@ $(PROJECT_DIR)/runtime/IntlCollatorConstructor.cpp
 $(PROJECT_DIR)/runtime/IntlCollatorPrototype.cpp
 $(PROJECT_DIR)/runtime/IntlDateTimeFormatConstructor.cpp
 $(PROJECT_DIR)/runtime/IntlDateTimeFormatPrototype.cpp
+$(PROJECT_DIR)/runtime/IntlLocalePrototype.cpp
 $(PROJECT_DIR)/runtime/IntlNumberFormatConstructor.cpp
 $(PROJECT_DIR)/runtime/IntlNumberFormatPrototype.cpp
 $(PROJECT_DIR)/runtime/IntlObject.cpp
index b1a33d5..79cde4e 100644 (file)
@@ -27,6 +27,7 @@ $(BUILT_PRODUCTS_DIR)/DerivedSources/JavaScriptCore/IntlCollatorConstructor.lut.
 $(BUILT_PRODUCTS_DIR)/DerivedSources/JavaScriptCore/IntlCollatorPrototype.lut.h
 $(BUILT_PRODUCTS_DIR)/DerivedSources/JavaScriptCore/IntlDateTimeFormatConstructor.lut.h
 $(BUILT_PRODUCTS_DIR)/DerivedSources/JavaScriptCore/IntlDateTimeFormatPrototype.lut.h
+$(BUILT_PRODUCTS_DIR)/DerivedSources/JavaScriptCore/IntlLocalePrototype.lut.h
 $(BUILT_PRODUCTS_DIR)/DerivedSources/JavaScriptCore/IntlNumberFormatConstructor.lut.h
 $(BUILT_PRODUCTS_DIR)/DerivedSources/JavaScriptCore/IntlNumberFormatPrototype.lut.h
 $(BUILT_PRODUCTS_DIR)/DerivedSources/JavaScriptCore/IntlObject.lut.h
index 1c2288d..00d2a57 100644 (file)
@@ -147,6 +147,7 @@ OBJECT_LUT_HEADERS = \
     IntlCollatorPrototype.lut.h \
     IntlDateTimeFormatConstructor.lut.h \
     IntlDateTimeFormatPrototype.lut.h \
+    IntlLocalePrototype.lut.h \
     IntlNumberFormatConstructor.lut.h \
     IntlNumberFormatPrototype.lut.h \
     IntlObject.lut.h \
index 216c927..027f7ef 100644 (file)
                A1E0451B1C25B4B100BB663C /* StringPrototype.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = StringPrototype.js; sourceTree = "<group>"; };
                A1FE1EB01C2C537E00A289FF /* DatePrototype.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = DatePrototype.js; sourceTree = "<group>"; };
                A27958D7FA1142B0AC9E364D /* WasmContextInlines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WasmContextInlines.h; sourceTree = "<group>"; };
+               A3AFF92B245A3CF900C9BA3B /* IntlLocale.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IntlLocale.h; sourceTree = "<group>"; };
+               A3AFF92C245A3CFA00C9BA3B /* IntlLocaleConstructor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IntlLocaleConstructor.h; sourceTree = "<group>"; };
+               A3AFF92D245A3CFA00C9BA3B /* IntlLocale.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = IntlLocale.cpp; sourceTree = "<group>"; };
+               A3AFF92E245A3CFB00C9BA3B /* IntlLocalePrototype.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = IntlLocalePrototype.cpp; sourceTree = "<group>"; };
+               A3AFF92F245A3CFB00C9BA3B /* IntlLocaleConstructor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = IntlLocaleConstructor.cpp; sourceTree = "<group>"; };
+               A3AFF930245A3CFC00C9BA3B /* IntlLocalePrototype.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IntlLocalePrototype.h; sourceTree = "<group>"; };
                A3BF884B24480BDE001B9F35 /* IntlRelativeTimeFormatPrototype.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = IntlRelativeTimeFormatPrototype.cpp; sourceTree = "<group>"; };
                A3BF884C24480BDF001B9F35 /* IntlRelativeTimeFormatPrototype.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IntlRelativeTimeFormatPrototype.h; sourceTree = "<group>"; };
                A3BF884D24480BDF001B9F35 /* IntlRelativeTimeFormatConstructor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = IntlRelativeTimeFormatConstructor.cpp; sourceTree = "<group>"; };
                                A1587D6A1B4DC14100D69849 /* IntlDateTimeFormatConstructor.h */,
                                A1587D6B1B4DC14100D69849 /* IntlDateTimeFormatPrototype.cpp */,
                                A1587D6C1B4DC14100D69849 /* IntlDateTimeFormatPrototype.h */,
+                               A3AFF92D245A3CFA00C9BA3B /* IntlLocale.cpp */,
+                               A3AFF92B245A3CF900C9BA3B /* IntlLocale.h */,
+                               A3AFF92F245A3CFB00C9BA3B /* IntlLocaleConstructor.cpp */,
+                               A3AFF92C245A3CFA00C9BA3B /* IntlLocaleConstructor.h */,
+                               A3AFF92E245A3CFB00C9BA3B /* IntlLocalePrototype.cpp */,
+                               A3AFF930245A3CFC00C9BA3B /* IntlLocalePrototype.h */,
                                A1D792F61B43864B004516F5 /* IntlNumberFormat.cpp */,
                                A1D792F71B43864B004516F5 /* IntlNumberFormat.h */,
                                A1D792F81B43864B004516F5 /* IntlNumberFormatConstructor.cpp */,
index 0f4c50d..ecce913 100644 (file)
@@ -812,6 +812,9 @@ runtime/IntlCollatorPrototype.cpp
 runtime/IntlDateTimeFormat.cpp
 runtime/IntlDateTimeFormatConstructor.cpp
 runtime/IntlDateTimeFormatPrototype.cpp
+runtime/IntlLocale.cpp
+runtime/IntlLocaleConstructor.cpp
+runtime/IntlLocalePrototype.cpp
 runtime/IntlNumberFormat.cpp
 runtime/IntlNumberFormatConstructor.cpp
 runtime/IntlNumberFormatPrototype.cpp
index d164ba4..8dbc6d7 100644 (file)
@@ -40,6 +40,7 @@
     macro(Infinity) \
     macro(Intl) \
     macro(Loader) \
+    macro(Locale) \
     macro(Map) \
     macro(NaN) \
     macro(Number) \
     macro(isWatchpoint) \
     macro(jettisonReason) \
     macro(join) \
+    macro(language) \
     macro(lastIndex) \
     macro(length) \
     macro(line) \
     macro(propertyIsEnumerable) \
     macro(prototype) \
     macro(raw) \
+    macro(region) \
     macro(replace) \
     macro(resolve) \
+    macro(script) \
     macro(second) \
     macro(sensitivity) \
     macro(set) \
diff --git a/Source/JavaScriptCore/runtime/IntlLocale.cpp b/Source/JavaScriptCore/runtime/IntlLocale.cpp
new file mode 100644 (file)
index 0000000..c2648bd
--- /dev/null
@@ -0,0 +1,491 @@
+/*
+ * Copyright (C) 2020 Sony Interactive Entertainment Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "IntlLocale.h"
+
+#include "Error.h"
+#include "IntlObject.h"
+#include "JSCInlines.h"
+#include "ObjectConstructor.h"
+#include <unicode/uloc.h>
+#include <wtf/unicode/icu/ICUHelpers.h>
+
+namespace JSC {
+
+const ClassInfo IntlLocale::s_info = { "Object", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(IntlLocale) };
+
+IntlLocale* IntlLocale::create(VM& vm, Structure* structure)
+{
+    auto* object = new (NotNull, allocateCell<IntlLocale>(vm.heap)) IntlLocale(vm, structure);
+    object->finishCreation(vm);
+    return object;
+}
+
+Structure* IntlLocale::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype)
+{
+    return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info());
+}
+
+IntlLocale::IntlLocale(VM& vm, Structure* structure)
+    : Base(vm, structure)
+{
+}
+
+void IntlLocale::finishCreation(VM& vm)
+{
+    Base::finishCreation(vm);
+    ASSERT(inherits(vm, info()));
+}
+
+void IntlLocale::visitChildren(JSCell* cell, SlotVisitor& visitor)
+{
+    auto* thisObject = jsCast<IntlLocale*>(cell);
+    ASSERT_GC_OBJECT_INHERITS(thisObject, info());
+
+    Base::visitChildren(thisObject, visitor);
+}
+
+class LocaleIDBuilder final {
+public:
+    bool initialize(const CString&);
+    CString toCanonical();
+
+    void overrideLanguageScriptRegion(StringView language, StringView script, StringView region);
+    void setKeywordValue(ASCIILiteral key, StringView value);
+
+private:
+    Vector<char, 32> m_buffer;
+};
+
+bool LocaleIDBuilder::initialize(const CString& tag)
+{
+    m_buffer = localeIDBufferForLanguageTag(tag);
+    return m_buffer.size();
+}
+
+CString LocaleIDBuilder::toCanonical()
+{
+    ASSERT(m_buffer.size());
+
+    UErrorCode status = U_ZERO_ERROR;
+    Vector<char, 32> result(32);
+    auto resultLength = uloc_canonicalize(m_buffer.data(), result.data(), result.size(), &status);
+    if (needsToGrowToProduceBuffer(status)) {
+        result.grow(resultLength);
+        status = U_ZERO_ERROR;
+        uloc_canonicalize(m_buffer.data(), result.data(), resultLength, &status);
+    }
+    if (U_FAILURE(status))
+        return CString();
+
+    return CString(result.data(), resultLength);
+}
+
+// Because ICU's C API doesn't have set[Language|Script|Region] functions...
+void LocaleIDBuilder::overrideLanguageScriptRegion(StringView language, StringView script, StringView region)
+{
+    unsigned length = strlen(m_buffer.data());
+    ASSERT(length);
+
+    StringView localeIDView { m_buffer.data(), length };
+
+    auto endOfLanguageScriptRegionVariant = localeIDView.find(ULOC_KEYWORD_SEPARATOR);
+    if (endOfLanguageScriptRegionVariant == notFound)
+        endOfLanguageScriptRegionVariant = length;
+
+    Vector<StringView> subtags;
+    for (auto subtag : localeIDView.left(endOfLanguageScriptRegionVariant).splitAllowingEmptyEntries('_'))
+        subtags.append(subtag);
+
+    if (!language.isNull())
+        subtags[0] = language;
+
+    bool hasScript = subtags.size() > 1 && subtags[1].length() == 4;
+    if (!script.isNull()) {
+        if (hasScript)
+            subtags[1] = script;
+        else {
+            subtags.insert(1, script);
+            hasScript = true;
+        }
+    }
+
+    if (!region.isNull()) {
+        size_t index = hasScript ? 2 : 1;
+        bool hasRegion = subtags.size() > index && subtags[index].length() < 4;
+        if (hasRegion)
+            subtags[index] = region;
+        else
+            subtags.insert(index, region);
+    }
+
+    Vector<char, 32> buffer;
+    bool hasAppended = false;
+    for (auto subtag : subtags) {
+        if (hasAppended)
+            buffer.append('_');
+        else
+            hasAppended = true;
+
+        ASSERT(subtag.is8Bit() && subtag.isAllASCII());
+        buffer.append(reinterpret_cast<const char*>(subtag.characters8()), subtag.length());
+    }
+
+    if (endOfLanguageScriptRegionVariant != length) {
+        auto rest = localeIDView.right(length - endOfLanguageScriptRegionVariant);
+
+        ASSERT(rest.is8Bit() && rest.isAllASCII());
+        buffer.append(reinterpret_cast<const char*>(rest.characters8()), rest.length());
+    }
+
+    buffer.append('\0');
+    m_buffer.swap(buffer);
+}
+
+void LocaleIDBuilder::setKeywordValue(ASCIILiteral key, StringView value)
+{
+    ASSERT(m_buffer.size());
+
+    ASSERT(value.is8Bit() && value.isAllASCII());
+    CString rawValue { reinterpret_cast<const char*>(value.characters8()), value.length() };
+
+    UErrorCode status = U_ZERO_ERROR;
+    auto length = uloc_setKeywordValue(key.characters(), rawValue.data(), m_buffer.data(), m_buffer.size(), &status);
+    // uloc_setKeywordValue does not set U_STRING_NOT_TERMINATED_WARNING.
+    if (needsToGrowToProduceBuffer(status)) {
+        m_buffer.grow(length + 1);
+        status = U_ZERO_ERROR;
+        uloc_setKeywordValue(key.characters(), rawValue.data(), m_buffer.data(), length + 1, &status);
+    }
+    ASSERT(U_SUCCESS(status));
+}
+
+String IntlLocale::keywordValue(ASCIILiteral key, bool isBoolean) const
+{
+    UErrorCode status = U_ZERO_ERROR;
+    Vector<char, 32> buffer(32);
+    auto bufferLength = uloc_getKeywordValue(m_localeID.data(), key.characters(), buffer.data(), buffer.size(), &status);
+    if (needsToGrowToProduceCString(status)) {
+        buffer.grow(bufferLength + 1);
+        status = U_ZERO_ERROR;
+        uloc_getKeywordValue(m_localeID.data(), key.characters(), buffer.data(), bufferLength + 1, &status);
+    }
+    ASSERT(U_SUCCESS(status));
+
+    const char* value = !isBoolean ? uloc_toUnicodeLocaleType(key.characters(), buffer.data()) : buffer.data();
+    return value ? String(value) : emptyString();
+}
+
+// unicode_language_subtag = alpha{2,3} | alpha{5,8} ;
+static bool isUnicodeLanguageSubtag(StringView string)
+{
+    auto length = string.length();
+    return length >= 2 && length <= 8 && length != 4 && string.isAllSpecialCharacters<isASCIIAlpha>();
+}
+
+// unicode_script_subtag = alpha{4} ;
+static bool isUnicodeScriptSubtag(StringView string)
+{
+    return string.length() == 4 && string.isAllSpecialCharacters<isASCIIAlpha>();
+}
+
+// unicode_region_subtag = alpha{2} | digit{3} ;
+static bool isUnicodeRegionSubtag(StringView string)
+{
+    auto length = string.length();
+    return (length == 2 && string.isAllSpecialCharacters<isASCIIAlpha>())
+        || (length == 3 && string.isAllSpecialCharacters<isASCIIDigit>());
+}
+
+// https://tc39.es/ecma402/#sec-Intl.Locale
+void IntlLocale::initializeLocale(JSGlobalObject* globalObject, JSValue tagValue, JSValue optionsValue)
+{
+    VM& vm = globalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    String tag = tagValue.inherits<IntlLocale>(vm) ? jsCast<IntlLocale*>(tagValue)->toString() : tagValue.toWTFString(globalObject);
+    RETURN_IF_EXCEPTION(scope, void());
+
+    JSValue options = optionsValue;
+    if (!optionsValue.isUndefined()) {
+        options = optionsValue.toObject(globalObject);
+        RETURN_IF_EXCEPTION(scope, void());
+    }
+
+    LocaleIDBuilder localeID;
+    if (!localeID.initialize(tag.utf8())) {
+        throwRangeError(globalObject, scope, "invalid language tag"_s);
+        return;
+    }
+
+    String language = intlStringOption(globalObject, options, vm.propertyNames->language, { }, nullptr, nullptr);
+    RETURN_IF_EXCEPTION(scope, void());
+    if (!language.isNull() && !isUnicodeLanguageSubtag(language)) {
+        throwRangeError(globalObject, scope, "language is not a well-formed language value"_s);
+        return;
+    }
+
+    String script = intlStringOption(globalObject, options, vm.propertyNames->script, { }, nullptr, nullptr);
+    RETURN_IF_EXCEPTION(scope, void());
+    if (!script.isNull() && !isUnicodeScriptSubtag(script)) {
+        throwRangeError(globalObject, scope, "script is not a well-formed script value"_s);
+        return;
+    }
+
+    String region = intlStringOption(globalObject, options, vm.propertyNames->region, { }, nullptr, nullptr);
+    RETURN_IF_EXCEPTION(scope, void());
+    if (!region.isNull() && !isUnicodeRegionSubtag(region)) {
+        throwRangeError(globalObject, scope, "region is not a well-formed region value"_s);
+        return;
+    }
+
+    if (!language.isNull() || !script.isNull() || !region.isNull())
+        localeID.overrideLanguageScriptRegion(language, script, region);
+
+    String calendar = intlStringOption(globalObject, options, vm.propertyNames->calendar, { }, nullptr, nullptr);
+    RETURN_IF_EXCEPTION(scope, void());
+    if (!calendar.isNull()) {
+        if (!isUnicodeLocaleIdentifierType(calendar)) {
+            throwRangeError(globalObject, scope, "calendar is not a well-formed calendar value"_s);
+            return;
+        }
+        localeID.setKeywordValue("calendar"_s, calendar);
+    }
+
+    String collation = intlStringOption(globalObject, options, vm.propertyNames->collation, { }, nullptr, nullptr);
+    RETURN_IF_EXCEPTION(scope, void());
+    if (!collation.isNull()) {
+        if (!isUnicodeLocaleIdentifierType(collation)) {
+            throwRangeError(globalObject, scope, "collation is not a well-formed collation value"_s);
+            return;
+        }
+        localeID.setKeywordValue("collation"_s, collation);
+    }
+
+    String hourCycle = intlStringOption(globalObject, options, vm.propertyNames->hourCycle, { "h11", "h12", "h23", "h24" }, "hourCycle must be \"h11\", \"h12\", \"h23\", or \"h24\"", nullptr);
+    RETURN_IF_EXCEPTION(scope, void());
+    if (!hourCycle.isNull())
+        localeID.setKeywordValue("hours"_s, hourCycle);
+
+    String caseFirst = intlStringOption(globalObject, options, vm.propertyNames->caseFirst, { "upper", "lower", "false" }, "caseFirst must be either \"upper\", \"lower\", or \"false\"", nullptr);
+    RETURN_IF_EXCEPTION(scope, void());
+    if (!caseFirst.isNull())
+        localeID.setKeywordValue("colcasefirst"_s, caseFirst);
+
+    TriState numeric = intlBooleanOption(globalObject, options, vm.propertyNames->numeric);
+    RETURN_IF_EXCEPTION(scope, void());
+    if (numeric != TriState::Indeterminate)
+        localeID.setKeywordValue("colnumeric"_s, numeric == TriState::True ? "yes" : "no");
+
+    String numberingSystem = intlStringOption(globalObject, options, vm.propertyNames->numberingSystem, { }, nullptr, nullptr);
+    RETURN_IF_EXCEPTION(scope, void());
+    if (!numberingSystem.isNull()) {
+        if (!isUnicodeLocaleIdentifierType(numberingSystem)) {
+            throwRangeError(globalObject, scope, "numberingSystem is not a well-formed numbering system value"_s);
+            return;
+        }
+        localeID.setKeywordValue("numbers"_s, numberingSystem);
+    }
+
+    m_localeID = localeID.toCanonical();
+    if (m_localeID.isNull()) {
+        throwTypeError(globalObject, scope, "failed to initialize Locale"_s);
+        return;
+    }
+}
+
+// https://tc39.es/ecma402/#sec-Intl.Locale.prototype.maximize
+const String& IntlLocale::maximize()
+{
+    if (m_maximized.isNull()) {
+        UErrorCode status = U_ZERO_ERROR;
+        Vector<char, 32> buffer(32);
+        auto bufferLength = uloc_addLikelySubtags(m_localeID.data(), buffer.data(), buffer.size(), &status);
+        if (needsToGrowToProduceCString(status)) {
+            buffer.grow(bufferLength + 1);
+            status = U_ZERO_ERROR;
+            uloc_addLikelySubtags(m_localeID.data(), buffer.data(), bufferLength + 1, &status);
+        }
+        ASSERT(U_SUCCESS(status));
+
+        m_maximized = languageTagForLocaleID(buffer.data());
+    }
+    return m_maximized;
+}
+
+// https://tc39.es/ecma402/#sec-Intl.Locale.prototype.minimize
+const String& IntlLocale::minimize()
+{
+    if (m_minimized.isNull()) {
+        UErrorCode status = U_ZERO_ERROR;
+        Vector<char, 32> buffer(32);
+        auto bufferLength = uloc_minimizeSubtags(m_localeID.data(), buffer.data(), buffer.size(), &status);
+        if (needsToGrowToProduceCString(status)) {
+            buffer.grow(bufferLength + 1);
+            status = U_ZERO_ERROR;
+            uloc_minimizeSubtags(m_localeID.data(), buffer.data(), bufferLength + 1, &status);
+        }
+        ASSERT(U_SUCCESS(status));
+
+        m_minimized = languageTagForLocaleID(buffer.data());
+    }
+    return m_minimized;
+}
+
+// https://tc39.es/ecma402/#sec-Intl.Locale.prototype.toString
+const String& IntlLocale::toString()
+{
+    if (m_fullString.isNull())
+        m_fullString = languageTagForLocaleID(m_localeID.data());
+    return m_fullString;
+}
+
+// https://tc39.es/ecma402/#sec-Intl.Locale.prototype.baseName
+const String& IntlLocale::baseName()
+{
+    if (m_baseName.isNull()) {
+        UErrorCode status = U_ZERO_ERROR;
+        Vector<char, 32> buffer(32);
+        auto bufferLength = uloc_getBaseName(m_localeID.data(), buffer.data(), buffer.size(), &status);
+        if (needsToGrowToProduceCString(status)) {
+            buffer.grow(bufferLength + 1);
+            status = U_ZERO_ERROR;
+            uloc_getBaseName(m_localeID.data(), buffer.data(), bufferLength + 1, &status);
+        }
+        ASSERT(U_SUCCESS(status));
+
+        m_baseName = languageTagForLocaleID(buffer.data());
+    }
+    return m_baseName;
+}
+
+// https://tc39.es/ecma402/#sec-Intl.Locale.prototype.language
+const String& IntlLocale::language()
+{
+    if (m_language.isNull()) {
+        UErrorCode status = U_ZERO_ERROR;
+        Vector<char, 8> buffer(8);
+        auto bufferLength = uloc_getLanguage(m_localeID.data(), buffer.data(), buffer.size(), &status);
+        if (needsToGrowToProduceBuffer(status)) {
+            buffer.grow(bufferLength);
+            status = U_ZERO_ERROR;
+            uloc_getLanguage(m_localeID.data(), buffer.data(), bufferLength, &status);
+        }
+        ASSERT(U_SUCCESS(status));
+
+        m_language = String(buffer.data(), bufferLength);
+    }
+    return m_language;
+}
+
+// https://tc39.es/ecma402/#sec-Intl.Locale.prototype.script
+const String& IntlLocale::script()
+{
+    if (m_script.isNull()) {
+        UErrorCode status = U_ZERO_ERROR;
+        Vector<char, 4> buffer(4);
+        auto bufferLength = uloc_getScript(m_localeID.data(), buffer.data(), buffer.size(), &status);
+        if (needsToGrowToProduceBuffer(status)) {
+            buffer.grow(bufferLength);
+            status = U_ZERO_ERROR;
+            uloc_getScript(m_localeID.data(), buffer.data(), bufferLength, &status);
+        }
+        ASSERT(U_SUCCESS(status));
+
+        m_script = String(buffer.data(), bufferLength);
+    }
+    return m_script;
+}
+
+// https://tc39.es/ecma402/#sec-Intl.Locale.prototype.region
+const String& IntlLocale::region()
+{
+    if (m_region.isNull()) {
+        UErrorCode status = U_ZERO_ERROR;
+        Vector<char, 3> buffer(3);
+        auto bufferLength = uloc_getCountry(m_localeID.data(), buffer.data(), buffer.size(), &status);
+        if (needsToGrowToProduceBuffer(status)) {
+            buffer.grow(bufferLength);
+            status = U_ZERO_ERROR;
+            uloc_getCountry(m_localeID.data(), buffer.data(), bufferLength, &status);
+        }
+        ASSERT(U_SUCCESS(status));
+
+        m_region = String(buffer.data(), bufferLength);
+    }
+    return m_region;
+}
+
+// https://tc39.es/ecma402/#sec-Intl.Locale.prototype.calendar
+const String& IntlLocale::calendar()
+{
+    if (m_calendar.isNull())
+        m_calendar = keywordValue("calendar"_s);
+    return m_calendar;
+}
+
+// https://tc39.es/ecma402/#sec-Intl.Locale.prototype.caseFirst
+const String& IntlLocale::caseFirst()
+{
+    if (m_caseFirst.isNull())
+        m_caseFirst = keywordValue("colcasefirst"_s);
+    return m_caseFirst;
+}
+
+// https://tc39.es/ecma402/#sec-Intl.Locale.prototype.collation
+const String& IntlLocale::collation()
+{
+    if (m_collation.isNull())
+        m_collation = keywordValue("collation"_s);
+    return m_collation;
+}
+
+// https://tc39.es/ecma402/#sec-Intl.Locale.prototype.hourCycle
+const String& IntlLocale::hourCycle()
+{
+    if (m_hourCycle.isNull())
+        m_hourCycle = keywordValue("hours"_s);
+    return m_hourCycle;
+}
+
+// https://tc39.es/ecma402/#sec-Intl.Locale.prototype.numberingSystem
+const String& IntlLocale::numberingSystem()
+{
+    if (m_numberingSystem.isNull())
+        m_numberingSystem = keywordValue("numbers"_s);
+    return m_numberingSystem;
+}
+
+// https://tc39.es/ecma402/#sec-Intl.Locale.prototype.numeric
+TriState IntlLocale::numeric()
+{
+    constexpr bool isBoolean = true;
+    if (m_numeric == TriState::Indeterminate)
+        m_numeric = triState(keywordValue("colnumeric"_s, isBoolean) == "yes");
+    return m_numeric;
+}
+
+} // namespace JSC
diff --git a/Source/JavaScriptCore/runtime/IntlLocale.h b/Source/JavaScriptCore/runtime/IntlLocale.h
new file mode 100644 (file)
index 0000000..afb69a2
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2020 Sony Interactive Entertainment Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "JSObject.h"
+
+namespace JSC {
+
+class IntlLocale final : public JSNonFinalObject {
+public:
+    using Base = JSNonFinalObject;
+
+    static constexpr bool needsDestruction = true;
+
+    static void destroy(JSCell* cell)
+    {
+        static_cast<IntlLocale*>(cell)->IntlLocale::~IntlLocale();
+    }
+
+    template<typename CellType, SubspaceAccess mode>
+    static IsoSubspace* subspaceFor(VM& vm)
+    {
+        return vm.intlLocaleSpace<mode>();
+    }
+
+    static IntlLocale* create(VM&, Structure*);
+    static Structure* createStructure(VM&, JSGlobalObject*, JSValue);
+
+    DECLARE_INFO;
+
+    void initializeLocale(JSGlobalObject*, JSValue tagValue, JSValue optionsValue);
+    const String& maximize();
+    const String& minimize();
+    const String& toString();
+    const String& baseName();
+    const String& language();
+    const String& script();
+    const String& region();
+    const String& calendar();
+    const String& caseFirst();
+    const String& collation();
+    const String& hourCycle();
+    const String& numberingSystem();
+    TriState numeric();
+
+private:
+    IntlLocale(VM&, Structure*);
+    void finishCreation(VM&);
+    static void visitChildren(JSCell*, SlotVisitor&);
+
+    String keywordValue(ASCIILiteral, bool isBoolean = false) const;
+
+    CString m_localeID;
+
+    String m_maximized;
+    String m_minimized;
+    String m_fullString;
+    String m_baseName;
+    String m_language;
+    String m_script;
+    String m_region;
+    String m_calendar;
+    String m_caseFirst;
+    String m_collation;
+    String m_hourCycle;
+    String m_numberingSystem;
+    TriState m_numeric { TriState::Indeterminate };
+};
+
+} // namespace JSC
diff --git a/Source/JavaScriptCore/runtime/IntlLocaleConstructor.cpp b/Source/JavaScriptCore/runtime/IntlLocaleConstructor.cpp
new file mode 100644 (file)
index 0000000..e669514
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2020 Sony Interactive Entertainment Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "IntlLocaleConstructor.h"
+
+#include "Error.h"
+#include "IntlLocale.h"
+#include "IntlLocalePrototype.h"
+#include "IntlObject.h"
+#include "JSCInlines.h"
+#include "Lookup.h"
+
+namespace JSC {
+
+STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(IntlLocaleConstructor);
+
+const ClassInfo IntlLocaleConstructor::s_info = { "Function", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(IntlLocaleConstructor) };
+
+IntlLocaleConstructor* IntlLocaleConstructor::create(VM& vm, Structure* structure, IntlLocalePrototype* localePrototype)
+{
+    auto* constructor = new (NotNull, allocateCell<IntlLocaleConstructor>(vm.heap)) IntlLocaleConstructor(vm, structure);
+    constructor->finishCreation(vm, localePrototype);
+    return constructor;
+}
+
+Structure* IntlLocaleConstructor::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype)
+{
+    return Structure::create(vm, globalObject, prototype, TypeInfo(InternalFunctionType, StructureFlags), info());
+}
+
+static EncodedJSValue JSC_HOST_CALL callIntlLocale(JSGlobalObject*, CallFrame*);
+static EncodedJSValue JSC_HOST_CALL constructIntlLocale(JSGlobalObject*, CallFrame*);
+
+IntlLocaleConstructor::IntlLocaleConstructor(VM& vm, Structure* structure)
+    : Base(vm, structure, callIntlLocale, constructIntlLocale)
+{
+}
+
+void IntlLocaleConstructor::finishCreation(VM& vm, IntlLocalePrototype* localePrototype)
+{
+    Base::finishCreation(vm, "Locale"_s, NameAdditionMode::WithoutStructureTransition);
+    putDirectWithoutTransition(vm, vm.propertyNames->prototype, localePrototype, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly);
+    putDirectWithoutTransition(vm, vm.propertyNames->length, jsNumber(1), PropertyAttribute::ReadOnly | PropertyAttribute::DontEnum);
+    localePrototype->putDirectWithoutTransition(vm, vm.propertyNames->constructor, this, static_cast<unsigned>(PropertyAttribute::DontEnum));
+}
+
+// https://tc39.es/ecma402/#sec-Intl.Locale
+static EncodedJSValue JSC_HOST_CALL constructIntlLocale(JSGlobalObject* globalObject, CallFrame* callFrame)
+{
+    VM& vm = globalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    JSObject* newTarget = asObject(callFrame->newTarget());
+    Structure* structure = newTarget == callFrame->jsCallee()
+        ? globalObject->localeStructure()
+        : InternalFunction::createSubclassStructure(globalObject, newTarget, getFunctionRealm(vm, newTarget)->localeStructure());
+    RETURN_IF_EXCEPTION(scope, { });
+
+    IntlLocale* locale = IntlLocale::create(vm, structure);
+    ASSERT(locale);
+
+    JSValue tag = callFrame->argument(0);
+    if (!tag.isString() && !tag.isObject())
+        return throwVMTypeError(globalObject, scope, "First argument to Intl.Locale must be a string or an object"_s);
+
+    scope.release();
+    locale->initializeLocale(globalObject, tag, callFrame->argument(1));
+    return JSValue::encode(locale);
+}
+
+// https://tc39.es/ecma402/#sec-Intl.Locale
+static EncodedJSValue JSC_HOST_CALL callIntlLocale(JSGlobalObject* globalObject, CallFrame*)
+{
+    VM& vm = globalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    return JSValue::encode(throwConstructorCannotBeCalledAsFunctionTypeError(globalObject, scope, "Locale"));
+}
+
+} // namespace JSC
diff --git a/Source/JavaScriptCore/runtime/IntlLocaleConstructor.h b/Source/JavaScriptCore/runtime/IntlLocaleConstructor.h
new file mode 100644 (file)
index 0000000..4b34a88
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2020 Sony Interactive Entertainment Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "InternalFunction.h"
+#include "IntlObject.h"
+
+namespace JSC {
+
+class IntlLocalePrototype;
+
+class IntlLocaleConstructor final : public InternalFunction {
+public:
+    using Base = InternalFunction;
+    static constexpr unsigned StructureFlags = Base::StructureFlags | HasStaticPropertyTable;
+
+    static IntlLocaleConstructor* create(VM&, Structure*, IntlLocalePrototype*);
+    static Structure* createStructure(VM&, JSGlobalObject*, JSValue);
+
+    DECLARE_INFO;
+
+private:
+    IntlLocaleConstructor(VM&, Structure*);
+    void finishCreation(VM&, IntlLocalePrototype*);
+};
+STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(IntlLocaleConstructor, InternalFunction);
+
+} // namespace JSC
diff --git a/Source/JavaScriptCore/runtime/IntlLocalePrototype.cpp b/Source/JavaScriptCore/runtime/IntlLocalePrototype.cpp
new file mode 100644 (file)
index 0000000..ddb5417
--- /dev/null
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2020 Sony Interactive Entertainment Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "IntlLocalePrototype.h"
+
+#include "Error.h"
+#include "IntlLocale.h"
+#include "JSCInlines.h"
+#include "JSObjectInlines.h"
+
+namespace JSC {
+
+static EncodedJSValue JSC_HOST_CALL IntlLocalePrototypeFuncMaximize(JSGlobalObject*, CallFrame*);
+static EncodedJSValue JSC_HOST_CALL IntlLocalePrototypeFuncMinimize(JSGlobalObject*, CallFrame*);
+static EncodedJSValue JSC_HOST_CALL IntlLocalePrototypeFuncToString(JSGlobalObject*, CallFrame*);
+static EncodedJSValue JSC_HOST_CALL IntlLocalePrototypeGetterBaseName(JSGlobalObject*, CallFrame*);
+static EncodedJSValue JSC_HOST_CALL IntlLocalePrototypeGetterCalendar(JSGlobalObject*, CallFrame*);
+static EncodedJSValue JSC_HOST_CALL IntlLocalePrototypeGetterCaseFirst(JSGlobalObject*, CallFrame*);
+static EncodedJSValue JSC_HOST_CALL IntlLocalePrototypeGetterCollation(JSGlobalObject*, CallFrame*);
+static EncodedJSValue JSC_HOST_CALL IntlLocalePrototypeGetterHourCycle(JSGlobalObject*, CallFrame*);
+static EncodedJSValue JSC_HOST_CALL IntlLocalePrototypeGetterNumeric(JSGlobalObject*, CallFrame*);
+static EncodedJSValue JSC_HOST_CALL IntlLocalePrototypeGetterNumberingSystem(JSGlobalObject*, CallFrame*);
+static EncodedJSValue JSC_HOST_CALL IntlLocalePrototypeGetterLanguage(JSGlobalObject*, CallFrame*);
+static EncodedJSValue JSC_HOST_CALL IntlLocalePrototypeGetterScript(JSGlobalObject*, CallFrame*);
+static EncodedJSValue JSC_HOST_CALL IntlLocalePrototypeGetterRegion(JSGlobalObject*, CallFrame*);
+
+}
+
+#include "IntlLocalePrototype.lut.h"
+
+namespace JSC {
+
+const ClassInfo IntlLocalePrototype::s_info = { "Intl.Locale", &Base::s_info, &localePrototypeTable, nullptr, CREATE_METHOD_TABLE(IntlLocalePrototype) };
+
+/* Source for IntlLocalePrototype.lut.h
+@begin localePrototypeTable
+  maximize         IntlLocalePrototypeFuncMaximize           DontEnum|Function 0
+  minimize         IntlLocalePrototypeFuncMinimize           DontEnum|Function 0
+  toString         IntlLocalePrototypeFuncToString           DontEnum|Function 0
+  baseName         IntlLocalePrototypeGetterBaseName         DontEnum|Accessor
+  calendar         IntlLocalePrototypeGetterCalendar         DontEnum|Accessor
+  caseFirst        IntlLocalePrototypeGetterCaseFirst        DontEnum|Accessor
+  collation        IntlLocalePrototypeGetterCollation        DontEnum|Accessor
+  hourCycle        IntlLocalePrototypeGetterHourCycle        DontEnum|Accessor
+  numeric          IntlLocalePrototypeGetterNumeric          DontEnum|Accessor
+  numberingSystem  IntlLocalePrototypeGetterNumberingSystem  DontEnum|Accessor
+  language         IntlLocalePrototypeGetterLanguage         DontEnum|Accessor
+  script           IntlLocalePrototypeGetterScript           DontEnum|Accessor
+  region           IntlLocalePrototypeGetterRegion           DontEnum|Accessor
+@end
+*/
+
+IntlLocalePrototype* IntlLocalePrototype::create(VM& vm, Structure* structure)
+{
+    auto* object = new (NotNull, allocateCell<IntlLocalePrototype>(vm.heap)) IntlLocalePrototype(vm, structure);
+    object->finishCreation(vm);
+    return object;
+}
+
+Structure* IntlLocalePrototype::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype)
+{
+    return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info());
+}
+
+IntlLocalePrototype::IntlLocalePrototype(VM& vm, Structure* structure)
+    : Base(vm, structure)
+{
+}
+
+void IntlLocalePrototype::finishCreation(VM& vm)
+{
+    Base::finishCreation(vm);
+    ASSERT(inherits(vm, info()));
+    JSC_TO_STRING_TAG_WITHOUT_TRANSITION();
+}
+
+// https://tc39.es/ecma402/#sec-Intl.Locale.prototype.maximize
+EncodedJSValue JSC_HOST_CALL IntlLocalePrototypeFuncMaximize(JSGlobalObject* globalObject, CallFrame* callFrame)
+{
+    VM& vm = globalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    auto* locale = jsDynamicCast<IntlLocale*>(vm, callFrame->thisValue());
+    if (!locale)
+        return throwVMTypeError(globalObject, scope, "Intl.Locale.prototype.maximize called on value that's not an object initialized as a Locale"_s);
+
+    RELEASE_AND_RETURN(scope, JSValue::encode(jsString(vm, locale->maximize())));
+}
+
+// https://tc39.es/ecma402/#sec-Intl.Locale.prototype.minimize
+EncodedJSValue JSC_HOST_CALL IntlLocalePrototypeFuncMinimize(JSGlobalObject* globalObject, CallFrame* callFrame)
+{
+    VM& vm = globalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    auto* locale = jsDynamicCast<IntlLocale*>(vm, callFrame->thisValue());
+    if (!locale)
+        return throwVMTypeError(globalObject, scope, "Intl.Locale.prototype.minimize called on value that's not an object initialized as a Locale"_s);
+
+    RELEASE_AND_RETURN(scope, JSValue::encode(jsString(vm, locale->minimize())));
+}
+
+// https://tc39.es/ecma402/#sec-Intl.Locale.prototype.toString
+EncodedJSValue JSC_HOST_CALL IntlLocalePrototypeFuncToString(JSGlobalObject* globalObject, CallFrame* callFrame)
+{
+    VM& vm = globalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    auto* locale = jsDynamicCast<IntlLocale*>(vm, callFrame->thisValue());
+    if (!locale)
+        return throwVMTypeError(globalObject, scope, "Intl.Locale.prototype.toString called on value that's not an object initialized as a Locale"_s);
+
+    RELEASE_AND_RETURN(scope, JSValue::encode(jsNontrivialString(vm, locale->toString())));
+}
+
+// https://tc39.es/ecma402/#sec-Intl.Locale.prototype.baseName
+EncodedJSValue JSC_HOST_CALL IntlLocalePrototypeGetterBaseName(JSGlobalObject* globalObject, CallFrame* callFrame)
+{
+    VM& vm = globalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    auto* locale = jsDynamicCast<IntlLocale*>(vm, callFrame->thisValue());
+    if (!locale)
+        return throwVMTypeError(globalObject, scope, "Intl.Locale.prototype.baseName called on value that's not an object initialized as a Locale"_s);
+
+    RELEASE_AND_RETURN(scope, JSValue::encode(jsNontrivialString(vm, locale->baseName())));
+}
+
+// https://tc39.es/ecma402/#sec-Intl.Locale.prototype.calendar
+EncodedJSValue JSC_HOST_CALL IntlLocalePrototypeGetterCalendar(JSGlobalObject* globalObject, CallFrame* callFrame)
+{
+    VM& vm = globalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    auto* locale = jsDynamicCast<IntlLocale*>(vm, callFrame->thisValue());
+    if (!locale)
+        return throwVMTypeError(globalObject, scope, "Intl.Locale.prototype.calendar called on value that's not an object initialized as a Locale"_s);
+
+    const String& calendar = locale->calendar();
+    RELEASE_AND_RETURN(scope, JSValue::encode(calendar.isEmpty() ? jsUndefined() : jsNontrivialString(vm, calendar)));
+}
+
+// https://tc39.es/ecma402/#sec-Intl.Locale.prototype.caseFirst
+EncodedJSValue JSC_HOST_CALL IntlLocalePrototypeGetterCaseFirst(JSGlobalObject* globalObject, CallFrame* callFrame)
+{
+    VM& vm = globalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    auto* locale = jsDynamicCast<IntlLocale*>(vm, callFrame->thisValue());
+    if (!locale)
+        return throwVMTypeError(globalObject, scope, "Intl.Locale.prototype.caseFirst called on value that's not an object initialized as a Locale"_s);
+
+    const String& caseFirst = locale->caseFirst();
+    RELEASE_AND_RETURN(scope, JSValue::encode(caseFirst.isEmpty() ? jsUndefined() : jsNontrivialString(vm, caseFirst)));
+}
+
+// https://tc39.es/ecma402/#sec-Intl.Locale.prototype.collation
+EncodedJSValue JSC_HOST_CALL IntlLocalePrototypeGetterCollation(JSGlobalObject* globalObject, CallFrame* callFrame)
+{
+    VM& vm = globalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    auto* locale = jsDynamicCast<IntlLocale*>(vm, callFrame->thisValue());
+    if (!locale)
+        return throwVMTypeError(globalObject, scope, "Intl.Locale.prototype.collation called on value that's not an object initialized as a Locale"_s);
+
+    const String& collation = locale->collation();
+    RELEASE_AND_RETURN(scope, JSValue::encode(collation.isEmpty() ? jsUndefined() : jsNontrivialString(vm, collation)));
+}
+
+// https://tc39.es/ecma402/#sec-Intl.Locale.prototype.hourCycle
+EncodedJSValue JSC_HOST_CALL IntlLocalePrototypeGetterHourCycle(JSGlobalObject* globalObject, CallFrame* callFrame)
+{
+    VM& vm = globalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    auto* locale = jsDynamicCast<IntlLocale*>(vm, callFrame->thisValue());
+    if (!locale)
+        return throwVMTypeError(globalObject, scope, "Intl.Locale.prototype.hourCycle called on value that's not an object initialized as a Locale"_s);
+
+    const String& hourCycle = locale->hourCycle();
+    RELEASE_AND_RETURN(scope, JSValue::encode(hourCycle.isEmpty() ? jsUndefined() : jsNontrivialString(vm, hourCycle)));
+}
+
+// https://tc39.es/ecma402/#sec-Intl.Locale.prototype.numeric
+EncodedJSValue JSC_HOST_CALL IntlLocalePrototypeGetterNumeric(JSGlobalObject* globalObject, CallFrame* callFrame)
+{
+    VM& vm = globalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    auto* locale = jsDynamicCast<IntlLocale*>(vm, callFrame->thisValue());
+    if (!locale)
+        return throwVMTypeError(globalObject, scope, "Intl.Locale.prototype.numeric called on value that's not an object initialized as a Locale"_s);
+
+    RELEASE_AND_RETURN(scope, JSValue::encode(jsBoolean(locale->numeric() == TriState::True)));
+}
+
+// https://tc39.es/ecma402/#sec-Intl.Locale.prototype.numberingSystem
+EncodedJSValue JSC_HOST_CALL IntlLocalePrototypeGetterNumberingSystem(JSGlobalObject* globalObject, CallFrame* callFrame)
+{
+    VM& vm = globalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    auto* locale = jsDynamicCast<IntlLocale*>(vm, callFrame->thisValue());
+    if (!locale)
+        return throwVMTypeError(globalObject, scope, "Intl.Locale.prototype.numberingSystem called on value that's not an object initialized as a Locale"_s);
+
+    const String& numberingSystem = locale->numberingSystem();
+    RELEASE_AND_RETURN(scope, JSValue::encode(numberingSystem.isEmpty() ? jsUndefined() : jsNontrivialString(vm, numberingSystem)));
+}
+
+// https://tc39.es/ecma402/#sec-Intl.Locale.prototype.language
+EncodedJSValue JSC_HOST_CALL IntlLocalePrototypeGetterLanguage(JSGlobalObject* globalObject, CallFrame* callFrame)
+{
+    VM& vm = globalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    auto* locale = jsDynamicCast<IntlLocale*>(vm, callFrame->thisValue());
+    if (!locale)
+        return throwVMTypeError(globalObject, scope, "Intl.Locale.prototype.language called on value that's not an object initialized as a Locale"_s);
+
+    RELEASE_AND_RETURN(scope, JSValue::encode(jsNontrivialString(vm, locale->language())));
+}
+
+// https://tc39.es/ecma402/#sec-Intl.Locale.prototype.script
+EncodedJSValue JSC_HOST_CALL IntlLocalePrototypeGetterScript(JSGlobalObject* globalObject, CallFrame* callFrame)
+{
+    VM& vm = globalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    auto* locale = jsDynamicCast<IntlLocale*>(vm, callFrame->thisValue());
+    if (!locale)
+        return throwVMTypeError(globalObject, scope, "Intl.Locale.prototype.script called on value that's not an object initialized as a Locale"_s);
+
+    const String& script = locale->script();
+    RELEASE_AND_RETURN(scope, JSValue::encode(script.isEmpty() ? jsUndefined() : jsNontrivialString(vm, script)));
+}
+
+// https://tc39.es/ecma402/#sec-Intl.Locale.prototype.region
+EncodedJSValue JSC_HOST_CALL IntlLocalePrototypeGetterRegion(JSGlobalObject* globalObject, CallFrame* callFrame)
+{
+    VM& vm = globalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    auto* locale = jsDynamicCast<IntlLocale*>(vm, callFrame->thisValue());
+    if (!locale)
+        return throwVMTypeError(globalObject, scope, "Intl.Locale.prototype.region called on value that's not an object initialized as a Locale"_s);
+
+    const String& region = locale->region();
+    RELEASE_AND_RETURN(scope, JSValue::encode(region.isEmpty() ? jsUndefined() : jsNontrivialString(vm, region)));
+}
+
+} // namespace JSC
diff --git a/Source/JavaScriptCore/runtime/IntlLocalePrototype.h b/Source/JavaScriptCore/runtime/IntlLocalePrototype.h
new file mode 100644 (file)
index 0000000..e52b003
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2020 Sony Interactive Entertainment Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "JSObject.h"
+
+namespace JSC {
+
+class IntlLocalePrototype final : public JSNonFinalObject {
+public:
+    using Base = JSNonFinalObject;
+    static constexpr unsigned StructureFlags = Base::StructureFlags | HasStaticPropertyTable;
+
+    template<typename CellType, SubspaceAccess>
+    static IsoSubspace* subspaceFor(VM& vm)
+    {
+        STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(IntlLocalePrototype, Base);
+        return &vm.plainObjectSpace;
+    }
+
+    static IntlLocalePrototype* create(VM&, Structure*);
+    static Structure* createStructure(VM&, JSGlobalObject*, JSValue);
+
+    DECLARE_INFO;
+
+private:
+    IntlLocalePrototype(VM&, Structure*);
+    void finishCreation(VM&);
+};
+
+} // namespace JSC
index 4b46ea9..571a997 100644 (file)
@@ -35,6 +35,9 @@
 #include "IntlCollatorPrototype.h"
 #include "IntlDateTimeFormatConstructor.h"
 #include "IntlDateTimeFormatPrototype.h"
+#include "IntlLocale.h"
+#include "IntlLocaleConstructor.h"
+#include "IntlLocalePrototype.h"
 #include "IntlNumberFormatConstructor.h"
 #include "IntlNumberFormatPrototype.h"
 #include "IntlPluralRulesConstructor.h"
@@ -136,6 +139,10 @@ IntlObject* IntlObject::create(VM& vm, JSGlobalObject* globalObject, Structure*
 void IntlObject::finishCreation(VM& vm, JSGlobalObject* globalObject)
 {
     Base::finishCreation(vm);
+    if (Options::useIntlLocale()) {
+        auto* localeConstructor = IntlLocaleConstructor::create(vm, IntlLocaleConstructor::createStructure(vm, globalObject, globalObject->functionPrototype()), jsCast<IntlLocalePrototype*>(globalObject->localeStructure()->storedPrototypeObject()));
+        putDirectWithoutTransition(vm, vm.propertyNames->Locale, localeConstructor, static_cast<unsigned>(PropertyAttribute::DontEnum));
+    }
     if (Options::useIntlRelativeTimeFormat()) {
         auto* relativeTimeFormatConstructor = IntlRelativeTimeFormatConstructor::create(vm, IntlRelativeTimeFormatConstructor::createStructure(vm, globalObject, globalObject->functionPrototype()), jsCast<IntlRelativeTimeFormatPrototype*>(globalObject->relativeTimeFormatStructure()->storedPrototypeObject()));
         putDirectWithoutTransition(vm, vm.propertyNames->RelativeTimeFormat, relativeTimeFormatConstructor, static_cast<unsigned>(PropertyAttribute::DontEnum));
@@ -147,7 +154,29 @@ Structure* IntlObject::createStructure(VM& vm, JSGlobalObject* globalObject, JSV
     return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info());
 }
 
-static String convertICULocaleToBCP47LanguageTag(const char* localeID)
+Vector<char, 32> localeIDBufferForLanguageTag(const CString& tag)
+{
+    if (!tag.length())
+        return { };
+
+    UErrorCode status = U_ZERO_ERROR;
+    Vector<char, 32> buffer(32);
+    int32_t parsedLength;
+    auto bufferLength = uloc_forLanguageTag(tag.data(), buffer.data(), buffer.size(), &parsedLength, &status);
+    if (needsToGrowToProduceCString(status)) {
+        // Before ICU 64, there's a chance uloc_forLanguageTag will "buffer overflow" while requesting a *smaller* size.
+        buffer.resize(bufferLength + 1);
+        status = U_ZERO_ERROR;
+        uloc_forLanguageTag(tag.data(), buffer.data(), bufferLength + 1, &parsedLength, &status);
+    }
+    if (U_FAILURE(status) || parsedLength != static_cast<int32_t>(tag.length()))
+        return { };
+
+    ASSERT(buffer.contains('\0'));
+    return buffer;
+}
+
+String languageTagForLocaleID(const char* localeID, bool isImmortal)
 {
     UErrorCode status = U_ZERO_ERROR;
     Vector<char, 32> buffer(32);
@@ -155,15 +184,17 @@ static String convertICULocaleToBCP47LanguageTag(const char* localeID)
     if (needsToGrowToProduceBuffer(status)) {
         buffer.grow(length);
         status = U_ZERO_ERROR;
-        uloc_toLanguageTag(localeID, buffer.data(), buffer.size(), false, &status);
+        uloc_toLanguageTag(localeID, buffer.data(), length, false, &status);
     }
-    if (!U_FAILURE(status)) {
-        // This is used to store into static variables that may be shared across 
-        // JSC execution threads. This must be immortal to make concurrent ref/deref
-        // safe.
+    if (U_FAILURE(status))
+        return String();
+
+    // This is used to store into static variables that may be shared across JSC execution threads.
+    // This must be immortal to make concurrent ref/deref safe.
+    if (isImmortal)
         return String(StringImpl::createStaticStringImpl(buffer.data(), length));
-    }
-    return String();
+
+    return String(buffer.data(), length);
 }
 
 const HashSet<String>& intlAvailableLocales()
@@ -174,9 +205,10 @@ const HashSet<String>& intlAvailableLocales()
     static std::once_flag initializeOnce;
     std::call_once(initializeOnce, [&] {
         ASSERT(availableLocales.isEmpty());
+        constexpr bool isImmortal = true;
         int32_t count = uloc_countAvailable();
         for (int32_t i = 0; i < count; ++i) {
-            String locale = convertICULocaleToBCP47LanguageTag(uloc_getAvailable(i));
+            String locale = languageTagForLocaleID(uloc_getAvailable(i), isImmortal);
             if (!locale.isEmpty())
                 availableLocales.add(locale);
         }
@@ -192,9 +224,10 @@ const HashSet<String>& intlCollatorAvailableLocales()
     static std::once_flag initializeOnce;
     std::call_once(initializeOnce, [&] {
         ASSERT(availableLocales.isEmpty());
+        constexpr bool isImmortal = true;
         int32_t count = ucol_countAvailable();
         for (int32_t i = 0; i < count; ++i) {
-            String locale = convertICULocaleToBCP47LanguageTag(ucol_getAvailable(i));
+            String locale = languageTagForLocaleID(ucol_getAvailable(i), isImmortal);
             if (!locale.isEmpty())
                 availableLocales.add(locale);
         }
@@ -316,40 +349,13 @@ bool isUnicodeLocaleIdentifierType(StringView string)
 
 // https://tc39.es/ecma402/#sec-isstructurallyvalidlanguagetag
 // https://tc39.es/ecma402/#sec-canonicalizeunicodelocaleid
-static String canonicalizeLanguageTag(const CString& input)
+static String canonicalizeLanguageTag(const CString& tag)
 {
-    if (!input.length())
+    auto buffer = localeIDBufferForLanguageTag(tag);
+    if (buffer.isEmpty())
         return String();
 
-    // We need to be careful with the output of uloc_forLanguageTag:
-    // - uloc_toLanguageTag doesn't take an input size param so we must ensure the string is null-terminated ourselves
-    // - before ICU 64, there's a chance that it will "buffer overflow" while requesting a *smaller* size
-    UErrorCode status = U_ZERO_ERROR;
-    Vector<char, 32> intermediate(32);
-    int32_t parsedLength;
-    auto intermediateLength = uloc_forLanguageTag(input.data(), intermediate.data(), intermediate.size(), &parsedLength, &status);
-    if (needsToGrowToProduceCString(status)) {
-        intermediate.resize(intermediateLength + 1);
-        status = U_ZERO_ERROR;
-        uloc_forLanguageTag(input.data(), intermediate.data(), intermediateLength + 1, &parsedLength, &status);
-    }
-    if (U_FAILURE(status) || parsedLength != static_cast<int32_t>(input.length()))
-        return String();
-
-    ASSERT(intermediate.contains('\0'));
-
-    status = U_ZERO_ERROR;
-    Vector<char, 32> result(32);
-    auto resultLength = uloc_toLanguageTag(intermediate.data(), result.data(), result.size(), false, &status);
-    if (needsToGrowToProduceBuffer(status)) {
-        result.grow(resultLength);
-        status = U_ZERO_ERROR;
-        uloc_toLanguageTag(intermediate.data(), result.data(), resultLength, false, &status);
-    }
-    if (U_FAILURE(status))
-        return String();
-
-    return String(result.data(), resultLength);
+    return languageTagForLocaleID(buffer.data());
 }
 
 Vector<String> canonicalizeLocaleList(JSGlobalObject* globalObject, JSValue locales)
@@ -366,7 +372,7 @@ Vector<String> canonicalizeLocaleList(JSGlobalObject* globalObject, JSValue loca
         return seen;
 
     JSObject* localesObject;
-    if (locales.isString()) {
+    if (locales.isString() || locales.inherits<IntlLocale>(vm)) {
         JSArray* localesArray = JSArray::tryCreate(vm, globalObject->arrayStructureForIndexingTypeDuringAllocation(ArrayWithContiguous));
         if (!localesArray) {
             throwOutOfMemoryError(globalObject, scope);
@@ -402,13 +408,18 @@ Vector<String> canonicalizeLocaleList(JSGlobalObject* globalObject, JSValue loca
                 return { };
             }
 
-            JSString* tag = kValue.toString(globalObject);
-            RETURN_IF_EXCEPTION(scope, Vector<String>());
+            Expected<CString, UTF8ConversionError> rawTag;
+            if (kValue.inherits<IntlLocale>(vm))
+                rawTag = jsCast<IntlLocale*>(kValue)->toString().tryGetUtf8();
+            else {
+                JSString* tag = kValue.toString(globalObject);
+                RETURN_IF_EXCEPTION(scope, Vector<String>());
 
-            auto tagValue = tag->value(globalObject);
-            RETURN_IF_EXCEPTION(scope, Vector<String>());
+                auto tagValue = tag->value(globalObject);
+                RETURN_IF_EXCEPTION(scope, Vector<String>());
 
-            auto rawTag = tagValue.tryGetUtf8();
+                rawTag = tagValue.tryGetUtf8();
+            }
             if (!rawTag) {
                 if (rawTag.error() == UTF8ConversionError::OutOfMemory)
                     throwOutOfMemoryError(globalObject, scope);
@@ -417,7 +428,7 @@ Vector<String> canonicalizeLocaleList(JSGlobalObject* globalObject, JSValue loca
 
             String canonicalizedTag = canonicalizeLanguageTag(rawTag.value());
             if (canonicalizedTag.isNull()) {
-                String errorMessage = tryMakeString("invalid language tag: ", tagValue);
+                String errorMessage = tryMakeString("invalid language tag: ", rawTag->data());
                 if (UNLIKELY(!errorMessage)) {
                     throwException(globalObject, scope, createOutOfMemoryError(globalObject));
                     return { };
@@ -483,7 +494,8 @@ String defaultLocale(JSGlobalObject* globalObject)
     static NeverDestroyed<String> icuDefaultLocalString;
     static std::once_flag initializeOnce;
     std::call_once(initializeOnce, [&] {
-        icuDefaultLocalString.get() = convertICULocaleToBCP47LanguageTag(uloc_getDefault());
+        constexpr bool isImmortal = true;
+        icuDefaultLocalString.get() = languageTagForLocaleID(uloc_getDefault(), isImmortal);
     });
     if (!icuDefaultLocalString->isEmpty())
         return icuDefaultLocalString.get();
index 5b78ebc..ed9c1e1 100644 (file)
@@ -68,6 +68,8 @@ TriState intlBooleanOption(JSGlobalObject*, JSValue options, PropertyName);
 String intlStringOption(JSGlobalObject*, JSValue options, PropertyName, std::initializer_list<const char*> values, const char* notFound, const char* fallback);
 unsigned intlNumberOption(JSGlobalObject*, JSValue options, PropertyName, unsigned minimum, unsigned maximum, unsigned fallback);
 unsigned intlDefaultNumberOption(JSGlobalObject*, JSValue, PropertyName, unsigned minimum, unsigned maximum, unsigned fallback);
+Vector<char, 32> localeIDBufferForLanguageTag(const CString&);
+String languageTagForLocaleID(const char*, bool isImmortal = false);
 Vector<String> canonicalizeLocaleList(JSGlobalObject*, JSValue locales);
 HashMap<String, String> resolveLocale(JSGlobalObject*, const HashSet<String>& availableLocales, const Vector<String>& requestedLocales, const HashMap<String, String>& options, const char* const relevantExtensionKeys[], size_t relevantExtensionKeyCount, Vector<String> (*localeData)(const String&, size_t));
 JSValue supportedLocales(JSGlobalObject*, const HashSet<String>& availableLocales, const Vector<String>& requestedLocales, JSValue options);
index 8446783..6651a02 100644 (file)
@@ -81,6 +81,8 @@
 #include "IntlCollatorPrototype.h"
 #include "IntlDateTimeFormat.h"
 #include "IntlDateTimeFormatPrototype.h"
+#include "IntlLocale.h"
+#include "IntlLocalePrototype.h"
 #include "IntlNumberFormat.h"
 #include "IntlNumberFormatPrototype.h"
 #include "IntlObject.h"
@@ -943,18 +945,24 @@ capitalName ## Constructor* lowerName ## Constructor = featureFlag ? capitalName
             IntlCollatorPrototype* collatorPrototype = IntlCollatorPrototype::create(init.vm, globalObject, IntlCollatorPrototype::createStructure(init.vm, globalObject, globalObject->objectPrototype()));
             init.set(IntlCollator::createStructure(init.vm, globalObject, collatorPrototype));
         });
-    m_numberFormatStructure.initLater(
-        [] (const Initializer<Structure>& init) {
-            JSGlobalObject* globalObject = jsCast<JSGlobalObject*>(init.owner);
-            IntlNumberFormatPrototype* numberFormatPrototype = IntlNumberFormatPrototype::create(init.vm, globalObject, IntlNumberFormatPrototype::createStructure(init.vm, globalObject, globalObject->objectPrototype()));
-            init.set(IntlNumberFormat::createStructure(init.vm, globalObject, numberFormatPrototype));
-        });
     m_dateTimeFormatStructure.initLater(
         [] (const Initializer<Structure>& init) {
             JSGlobalObject* globalObject = jsCast<JSGlobalObject*>(init.owner);
             IntlDateTimeFormatPrototype* dateTimeFormatPrototype = IntlDateTimeFormatPrototype::create(init.vm, globalObject, IntlDateTimeFormatPrototype::createStructure(init.vm, globalObject, globalObject->objectPrototype()));
             init.set(IntlDateTimeFormat::createStructure(init.vm, globalObject, dateTimeFormatPrototype));
         });
+    m_localeStructure.initLater(
+        [] (const Initializer<Structure>& init) {
+            JSGlobalObject* globalObject = jsCast<JSGlobalObject*>(init.owner);
+            IntlLocalePrototype* localePrototype = IntlLocalePrototype::create(init.vm, IntlLocalePrototype::createStructure(init.vm, globalObject, globalObject->objectPrototype()));
+            init.set(IntlLocale::createStructure(init.vm, globalObject, localePrototype));
+        });
+    m_numberFormatStructure.initLater(
+        [] (const Initializer<Structure>& init) {
+            JSGlobalObject* globalObject = jsCast<JSGlobalObject*>(init.owner);
+            IntlNumberFormatPrototype* numberFormatPrototype = IntlNumberFormatPrototype::create(init.vm, globalObject, IntlNumberFormatPrototype::createStructure(init.vm, globalObject, globalObject->objectPrototype()));
+            init.set(IntlNumberFormat::createStructure(init.vm, globalObject, numberFormatPrototype));
+        });
     m_pluralRulesStructure.initLater(
         [] (const Initializer<Structure>& init) {
             JSGlobalObject* globalObject = jsCast<JSGlobalObject*>(init.owner);
@@ -1775,8 +1783,9 @@ void JSGlobalObject::visitChildren(JSCell* cell, SlotVisitor& visitor)
 
     thisObject->m_defaultCollator.visit(visitor);
     thisObject->m_collatorStructure.visit(visitor);
-    thisObject->m_numberFormatStructure.visit(visitor);
     thisObject->m_dateTimeFormatStructure.visit(visitor);
+    thisObject->m_numberFormatStructure.visit(visitor);
+    thisObject->m_localeStructure.visit(visitor);
     thisObject->m_pluralRulesStructure.visit(visitor);
     thisObject->m_relativeTimeFormatStructure.visit(visitor);
 
index a0c3b4a..66a3cbe 100644 (file)
@@ -291,8 +291,9 @@ public:
 
     LazyProperty<JSGlobalObject, IntlCollator> m_defaultCollator;
     LazyProperty<JSGlobalObject, Structure> m_collatorStructure;
-    LazyProperty<JSGlobalObject, Structure> m_numberFormatStructure;
     LazyProperty<JSGlobalObject, Structure> m_dateTimeFormatStructure;
+    LazyProperty<JSGlobalObject, Structure> m_localeStructure;
+    LazyProperty<JSGlobalObject, Structure> m_numberFormatStructure;
     LazyProperty<JSGlobalObject, Structure> m_pluralRulesStructure;
     LazyProperty<JSGlobalObject, Structure> m_relativeTimeFormatStructure;
 
@@ -786,8 +787,9 @@ public:
     Structure* webAssemblyWrapperFunctionStructure() const { return m_webAssemblyWrapperFunctionStructure.get(this); }
 #endif // ENABLE(WEBASSEMBLY)
     Structure* collatorStructure() { return m_collatorStructure.get(this); }
-    Structure* numberFormatStructure() { return m_numberFormatStructure.get(this); }
     Structure* dateTimeFormatStructure() { return m_dateTimeFormatStructure.get(this); }
+    Structure* numberFormatStructure() { return m_numberFormatStructure.get(this); }
+    Structure* localeStructure() { return m_localeStructure.get(this); }
     Structure* pluralRulesStructure() { return m_pluralRulesStructure.get(this); }
     Structure* relativeTimeFormatStructure() { return m_relativeTimeFormatStructure.get(this); }
 
index 167a211..f9a8bd5 100644 (file)
@@ -486,6 +486,7 @@ constexpr bool enableWebAssemblyStreamingApi = false;
     v(Bool, useWebAssemblyMultiValues, true, Normal, "Allow types from the wasm mulit-values spec.") \
     v(Bool, useWeakRefs, false, Normal, "Expose the WeakRef constructor.") \
     v(Bool, useBigInt, true, Normal, "If true, we will enable BigInt support.") \
+    v(Bool, useIntlLocale, false, Normal, "Expose the Intl.Locale constructor.") \
     v(Bool, useIntlRelativeTimeFormat, false, Normal, "Expose the Intl.RelativeTimeFormat constructor.") \
     v(Bool, useArrayAllocationProfiling, true, Normal, "If true, we will use our normal array allocation profiling. If false, the allocation profile will always claim to be undecided.") \
     v(Bool, forcePolyProto, false, Normal, "If true, create_this will always create an object with a poly proto structure.") \
index 4b96dd5..cdc08b6 100644 (file)
@@ -75,6 +75,7 @@
 #include "Interpreter.h"
 #include "IntlCollator.h"
 #include "IntlDateTimeFormat.h"
+#include "IntlLocale.h"
 #include "IntlNumberFormat.h"
 #include "IntlPluralRules.h"
 #include "IntlRelativeTimeFormat.h"
@@ -339,6 +340,7 @@ VM::VM(VMType vmType, HeapType heapType)
 #endif
     , intlCollatorHeapCellType(IsoHeapCellType::create<IntlCollator>())
     , intlDateTimeFormatHeapCellType(IsoHeapCellType::create<IntlDateTimeFormat>())
+    , intlLocaleHeapCellType(IsoHeapCellType::create<IntlLocale>())
     , intlNumberFormatHeapCellType(IsoHeapCellType::create<IntlNumberFormat>())
     , intlPluralRulesHeapCellType(IsoHeapCellType::create<IntlPluralRules>())
     , intlRelativeTimeFormatHeapCellType(IsoHeapCellType::create<IntlRelativeTimeFormat>())
@@ -1510,6 +1512,7 @@ DYNAMIC_ISO_SUBSPACE_DEFINE_MEMBER_SLOW(callbackAPIWrapperGlobalObjectSpace, cal
 #endif
 DYNAMIC_ISO_SUBSPACE_DEFINE_MEMBER_SLOW(intlCollatorSpace, intlCollatorHeapCellType.get(), IntlCollator)
 DYNAMIC_ISO_SUBSPACE_DEFINE_MEMBER_SLOW(intlDateTimeFormatSpace, intlDateTimeFormatHeapCellType.get(), IntlDateTimeFormat)
+DYNAMIC_ISO_SUBSPACE_DEFINE_MEMBER_SLOW(intlLocaleSpace, intlLocaleHeapCellType.get(), IntlLocale)
 DYNAMIC_ISO_SUBSPACE_DEFINE_MEMBER_SLOW(intlNumberFormatSpace, intlNumberFormatHeapCellType.get(), IntlNumberFormat)
 DYNAMIC_ISO_SUBSPACE_DEFINE_MEMBER_SLOW(intlPluralRulesSpace, intlPluralRulesHeapCellType.get(), IntlPluralRules)
 DYNAMIC_ISO_SUBSPACE_DEFINE_MEMBER_SLOW(intlRelativeTimeFormatSpace, intlRelativeTimeFormatHeapCellType.get(), IntlRelativeTimeFormat)
index 5b7a6f5..58f14f9 100644 (file)
@@ -126,6 +126,7 @@ class Identifier;
 class Interpreter;
 class IntlCollator;
 class IntlDateTimeFormat;
+class IntlLocale;
 class IntlNumberFormat;
 class IntlPluralRules;
 class IntlRelativeTimeFormat;
@@ -400,6 +401,7 @@ public:
 #endif
     std::unique_ptr<IsoHeapCellType> intlCollatorHeapCellType;
     std::unique_ptr<IsoHeapCellType> intlDateTimeFormatHeapCellType;
+    std::unique_ptr<IsoHeapCellType> intlLocaleHeapCellType;
     std::unique_ptr<IsoHeapCellType> intlNumberFormatHeapCellType;
     std::unique_ptr<IsoHeapCellType> intlPluralRulesHeapCellType;
     std::unique_ptr<IsoHeapCellType> intlRelativeTimeFormatHeapCellType;
@@ -557,6 +559,7 @@ public:
     DYNAMIC_ISO_SUBSPACE_DEFINE_MEMBER(withScopeSpace)
     DYNAMIC_ISO_SUBSPACE_DEFINE_MEMBER(intlCollatorSpace)
     DYNAMIC_ISO_SUBSPACE_DEFINE_MEMBER(intlDateTimeFormatSpace)
+    DYNAMIC_ISO_SUBSPACE_DEFINE_MEMBER(intlLocaleSpace)
     DYNAMIC_ISO_SUBSPACE_DEFINE_MEMBER(intlNumberFormatSpace)
     DYNAMIC_ISO_SUBSPACE_DEFINE_MEMBER(intlPluralRulesSpace)
     DYNAMIC_ISO_SUBSPACE_DEFINE_MEMBER(intlRelativeTimeFormatSpace)
index dfcdc94..8592efe 100644 (file)
@@ -1,3 +1,13 @@
+2020-05-05  Ross Kirsling  <ross.kirsling@sony.com>
+
+        [ECMA-402] Implement Intl.Locale
+        https://bugs.webkit.org/show_bug.cgi?id=209772
+
+        Reviewed by Darin Adler and Saam Barati.
+
+        * Scripts/run-jsc-stress-tests:
+        Add runIntlLocaleEnabled.
+
 2020-05-05  Saam Barati  <sbarati@apple.com>
 
         Don't use the DebugHeap for catalyst
index 67e4515..5f0f5f1 100755 (executable)
@@ -715,6 +715,10 @@ def runBigIntEnabledBaseline(*optionalTestSpecificOptions)
     run("big-int-enabled-baseline", "--useBigInt=true", "--useDFGJIT=0", *optionalTestSpecificOptions)
 end
 
+def runIntlLocaleEnabled(*optionalTestSpecificOptions)
+    run("intl-locale-enabled", "--useIntlLocale=true" , *(FTL_OPTIONS + optionalTestSpecificOptions))
+end
+
 def runIntlRelativeTimeFormatEnabled(*optionalTestSpecificOptions)
     run("intl-relativetimeformat-enabled", "--useIntlRelativeTimeFormat=true" , *(FTL_OPTIONS + optionalTestSpecificOptions))
 end