[Intl] Sync ListFormat to latest spec.
1. Add StringListFromIterable based on https://tc39.es/proposal-intl-list-format/#sec-createstringlistfromiterable 2. Adjust other parts to sync with the new version. Bug: v8:9747 Change-Id: I14202f2963463e6a3e9816209f087bfe8e73cb91 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1809902 Commit-Queue: Frank Tang <ftang@chromium.org> Reviewed-by: Jakob Kummerow <jkummerow@chromium.org> Cr-Commit-Position: refs/heads/master@{#63899}
This commit is contained in:
parent
dc1a93b85c
commit
8d1b4774bf
@ -619,6 +619,9 @@ namespace internal {
|
|||||||
TFS(IterableToListWithSymbolLookup, kIterable) \
|
TFS(IterableToListWithSymbolLookup, kIterable) \
|
||||||
TFS(IterableToListMayPreserveHoles, kIterable, kIteratorFn) \
|
TFS(IterableToListMayPreserveHoles, kIterable, kIteratorFn) \
|
||||||
\
|
\
|
||||||
|
/* #sec-createstringlistfromiterable */ \
|
||||||
|
TFS(StringListFromIterable, kIterable) \
|
||||||
|
\
|
||||||
/* Map */ \
|
/* Map */ \
|
||||||
TFS(FindOrderedHashMapEntry, kTable, kKey) \
|
TFS(FindOrderedHashMapEntry, kTable, kKey) \
|
||||||
TFJ(MapConstructor, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \
|
TFJ(MapConstructor, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \
|
||||||
|
@ -162,28 +162,15 @@ void IntlBuiltinsAssembler::ListFormatCommon(TNode<Context> context,
|
|||||||
method_name);
|
method_name);
|
||||||
TNode<JSListFormat> list_format = CAST(receiver);
|
TNode<JSListFormat> list_format = CAST(receiver);
|
||||||
|
|
||||||
// 4. If list is not provided or is undefined, then
|
|
||||||
TNode<Object> list = args.GetOptionalArgumentValue(0);
|
TNode<Object> list = args.GetOptionalArgumentValue(0);
|
||||||
Label has_list(this);
|
|
||||||
{
|
{
|
||||||
GotoIfNot(IsUndefined(list), &has_list);
|
// 4. Let stringList be ? StringListFromIterable(list).
|
||||||
if (format_func_id == Runtime::kFormatList) {
|
TNode<Object> string_list =
|
||||||
// a. Return an empty String.
|
CallBuiltin(Builtins::kStringListFromIterable, context, list);
|
||||||
args.PopAndReturn(EmptyStringConstant());
|
|
||||||
} else {
|
|
||||||
DCHECK_EQ(format_func_id, Runtime::kFormatListToParts);
|
|
||||||
// a. Return an empty Array.
|
|
||||||
args.PopAndReturn(AllocateEmptyJSArray(context));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BIND(&has_list);
|
|
||||||
{
|
|
||||||
// 5. Let x be ? IterableToList(list).
|
|
||||||
TNode<Object> x =
|
|
||||||
CallBuiltin(Builtins::kIterableToListWithSymbolLookup, context, list);
|
|
||||||
|
|
||||||
// 6. Return ? FormatList(lf, x).
|
// 6. Return ? FormatList(lf, stringList).
|
||||||
args.PopAndReturn(CallRuntime(format_func_id, context, list_format, x));
|
args.PopAndReturn(
|
||||||
|
CallRuntime(format_func_id, context, list_format, string_list));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -241,6 +241,69 @@ TF_BUILTIN(IterableToList, IteratorBuiltinsAssembler) {
|
|||||||
Return(IterableToList(context, iterable, iterator_fn));
|
Return(IterableToList(context, iterable, iterator_fn));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TNode<JSArray> IteratorBuiltinsAssembler::StringListFromIterable(
|
||||||
|
TNode<Context> context, TNode<Object> iterable) {
|
||||||
|
Label done(this);
|
||||||
|
GrowableFixedArray list(state());
|
||||||
|
// 1. If iterable is undefined, then
|
||||||
|
// a. Return a new empty List.
|
||||||
|
GotoIf(IsUndefined(iterable), &done);
|
||||||
|
|
||||||
|
// 2. Let iteratorRecord be ? GetIterator(items).
|
||||||
|
IteratorRecord iterator_record = GetIterator(context, iterable);
|
||||||
|
|
||||||
|
// 3. Let list be a new empty List.
|
||||||
|
|
||||||
|
Variable* vars[] = {list.var_array(), list.var_length(), list.var_capacity()};
|
||||||
|
Label loop_start(this, 3, vars);
|
||||||
|
Goto(&loop_start);
|
||||||
|
// 4. Let next be true.
|
||||||
|
// 5. Repeat, while next is not false
|
||||||
|
Label if_isnotstringtype(this, Label::kDeferred),
|
||||||
|
if_exception(this, Label::kDeferred);
|
||||||
|
BIND(&loop_start);
|
||||||
|
{
|
||||||
|
// a. Set next to ? IteratorStep(iteratorRecord).
|
||||||
|
TNode<JSReceiver> next = IteratorStep(context, iterator_record, &done);
|
||||||
|
// b. If next is not false, then
|
||||||
|
// i. Let nextValue be ? IteratorValue(next).
|
||||||
|
TNode<Object> next_value = IteratorValue(context, next);
|
||||||
|
// ii. If Type(nextValue) is not String, then
|
||||||
|
GotoIf(TaggedIsSmi(next_value), &if_isnotstringtype);
|
||||||
|
TNode<Uint16T> next_value_type = LoadInstanceType(CAST(next_value));
|
||||||
|
GotoIfNot(IsStringInstanceType(next_value_type), &if_isnotstringtype);
|
||||||
|
// iii. Append nextValue to the end of the List list.
|
||||||
|
list.Push(next_value);
|
||||||
|
Goto(&loop_start);
|
||||||
|
// 5.b.ii
|
||||||
|
BIND(&if_isnotstringtype);
|
||||||
|
{
|
||||||
|
// 1. Let error be ThrowCompletion(a newly created TypeError object).
|
||||||
|
TVARIABLE(Object, var_exception);
|
||||||
|
TNode<Object> ret = CallRuntime(
|
||||||
|
Runtime::kThrowTypeError, context,
|
||||||
|
SmiConstant(MessageTemplate::kIterableYieldedNonString), next_value);
|
||||||
|
GotoIfException(ret, &if_exception, &var_exception);
|
||||||
|
Unreachable();
|
||||||
|
|
||||||
|
// 2. Return ? IteratorClose(iteratorRecord, error).
|
||||||
|
BIND(&if_exception);
|
||||||
|
IteratorCloseOnException(context, iterator_record, var_exception.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BIND(&done);
|
||||||
|
// 6. Return list.
|
||||||
|
return list.ToJSArray(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
TF_BUILTIN(StringListFromIterable, IteratorBuiltinsAssembler) {
|
||||||
|
TNode<Context> context = CAST(Parameter(Descriptor::kContext));
|
||||||
|
TNode<Object> iterable = CAST(Parameter(Descriptor::kIterable));
|
||||||
|
|
||||||
|
Return(StringListFromIterable(context, iterable));
|
||||||
|
}
|
||||||
|
|
||||||
// This builtin always returns a new JSArray and is thus safe to use even in the
|
// This builtin always returns a new JSArray and is thus safe to use even in the
|
||||||
// presence of code that may call back into user-JS. This builtin will take the
|
// presence of code that may call back into user-JS. This builtin will take the
|
||||||
// fast path if the iterable is a fast array and the Array prototype and the
|
// fast path if the iterable is a fast array and the Array prototype and the
|
||||||
|
@ -68,6 +68,11 @@ class IteratorBuiltinsAssembler : public CodeStubAssembler {
|
|||||||
TNode<JSArray> IterableToList(TNode<Context> context, TNode<Object> iterable,
|
TNode<JSArray> IterableToList(TNode<Context> context, TNode<Object> iterable,
|
||||||
TNode<Object> iterator_fn);
|
TNode<Object> iterator_fn);
|
||||||
|
|
||||||
|
// Currently at https://tc39.github.io/proposal-intl-list-format/
|
||||||
|
// #sec-createstringlistfromiterable
|
||||||
|
TNode<JSArray> StringListFromIterable(TNode<Context> context,
|
||||||
|
TNode<Object> iterable);
|
||||||
|
|
||||||
void FastIterableToList(TNode<Context> context, TNode<Object> iterable,
|
void FastIterableToList(TNode<Context> context, TNode<Object> iterable,
|
||||||
TVariable<Object>* var_result, Label* slow);
|
TVariable<Object>* var_result, Label* slow);
|
||||||
};
|
};
|
||||||
|
@ -37,6 +37,9 @@ namespace iterator {
|
|||||||
extern macro IteratorBuiltinsAssembler::IterableToList(
|
extern macro IteratorBuiltinsAssembler::IterableToList(
|
||||||
implicit context: Context)(JSAny, JSAny): JSArray;
|
implicit context: Context)(JSAny, JSAny): JSArray;
|
||||||
|
|
||||||
|
extern macro IteratorBuiltinsAssembler::StringListFromIterable(
|
||||||
|
implicit context: Context)(JSAny): JSArray;
|
||||||
|
|
||||||
extern builtin IterableToListMayPreserveHoles(implicit context:
|
extern builtin IterableToListMayPreserveHoles(implicit context:
|
||||||
Context)(JSAny, JSAny);
|
Context)(JSAny, JSAny);
|
||||||
extern builtin IterableToListWithSymbolLookup(implicit context:
|
extern builtin IterableToListWithSymbolLookup(implicit context:
|
||||||
|
@ -33,7 +33,6 @@ namespace internal {
|
|||||||
"Derived ArrayBuffer constructor created a buffer which was too small") \
|
"Derived ArrayBuffer constructor created a buffer which was too small") \
|
||||||
T(ArrayBufferSpeciesThis, \
|
T(ArrayBufferSpeciesThis, \
|
||||||
"ArrayBuffer subclass returned this from species constructor") \
|
"ArrayBuffer subclass returned this from species constructor") \
|
||||||
T(ArrayItemNotType, "array %[%] is not type %") \
|
|
||||||
T(AwaitNotInAsyncFunction, "await is only valid in async function") \
|
T(AwaitNotInAsyncFunction, "await is only valid in async function") \
|
||||||
T(AtomicsWaitNotAllowed, "Atomics.wait cannot be called in this context") \
|
T(AtomicsWaitNotAllowed, "Atomics.wait cannot be called in this context") \
|
||||||
T(BadSortComparisonFunction, \
|
T(BadSortComparisonFunction, \
|
||||||
@ -100,6 +99,7 @@ namespace internal {
|
|||||||
T(InvalidRegExpExecResult, \
|
T(InvalidRegExpExecResult, \
|
||||||
"RegExp exec method returned something other than an Object or null") \
|
"RegExp exec method returned something other than an Object or null") \
|
||||||
T(InvalidUnit, "Invalid unit argument for %() '%'") \
|
T(InvalidUnit, "Invalid unit argument for %() '%'") \
|
||||||
|
T(IterableYieldedNonString, "Iterable yielded % which is not a string") \
|
||||||
T(IteratorResultNotAnObject, "Iterator result % is not an object") \
|
T(IteratorResultNotAnObject, "Iterator result % is not an object") \
|
||||||
T(IteratorSymbolNonCallable, "Found non-callable @@iterator") \
|
T(IteratorSymbolNonCallable, "Found non-callable @@iterator") \
|
||||||
T(IteratorValueNotAnObject, "Iterator value % is not an entry object") \
|
T(IteratorValueNotAnObject, "Iterator value % is not an entry object") \
|
||||||
|
@ -252,35 +252,18 @@ namespace {
|
|||||||
// Extract String from JSArray into array of UnicodeString
|
// Extract String from JSArray into array of UnicodeString
|
||||||
Maybe<std::vector<icu::UnicodeString>> ToUnicodeStringArray(
|
Maybe<std::vector<icu::UnicodeString>> ToUnicodeStringArray(
|
||||||
Isolate* isolate, Handle<JSArray> array) {
|
Isolate* isolate, Handle<JSArray> array) {
|
||||||
Factory* factory = isolate->factory();
|
|
||||||
// In general, ElementsAccessor::Get actually isn't guaranteed to give us the
|
// In general, ElementsAccessor::Get actually isn't guaranteed to give us the
|
||||||
// elements in order. But if it is a holey array, it will cause the exception
|
// elements in order. But if it is a holey array, it will cause the exception
|
||||||
// with the IsString check.
|
// with the IsString check.
|
||||||
auto* accessor = array->GetElementsAccessor();
|
auto* accessor = array->GetElementsAccessor();
|
||||||
uint32_t length = accessor->NumberOfElements(*array);
|
uint32_t length = accessor->NumberOfElements(*array);
|
||||||
|
|
||||||
// ecma402 #sec-createpartsfromlist
|
|
||||||
// 2. If list contains any element value such that Type(value) is not String,
|
|
||||||
// throw a TypeError exception.
|
|
||||||
//
|
|
||||||
// Per spec it looks like we're supposed to throw a TypeError exception if the
|
|
||||||
// item isn't already a string, rather than coercing to a string.
|
|
||||||
std::vector<icu::UnicodeString> result;
|
std::vector<icu::UnicodeString> result;
|
||||||
for (uint32_t i = 0; i < length; i++) {
|
for (uint32_t i = 0; i < length; i++) {
|
||||||
DCHECK(accessor->HasElement(*array, i));
|
DCHECK(accessor->HasElement(*array, i));
|
||||||
Handle<Object> item = accessor->Get(array, i);
|
Handle<Object> item = accessor->Get(array, i);
|
||||||
DCHECK(!item.is_null());
|
DCHECK(!item.is_null());
|
||||||
if (!item->IsString()) {
|
DCHECK(item->IsString());
|
||||||
THROW_NEW_ERROR_RETURN_VALUE(
|
|
||||||
isolate,
|
|
||||||
NewTypeError(MessageTemplate::kArrayItemNotType,
|
|
||||||
factory->list_string(),
|
|
||||||
// TODO(ftang): For dictionary-mode arrays, i isn't
|
|
||||||
// actually the index in the array but the index in the
|
|
||||||
// dictionary.
|
|
||||||
factory->NewNumber(i), factory->String_string()),
|
|
||||||
Nothing<std::vector<icu::UnicodeString>>());
|
|
||||||
}
|
|
||||||
Handle<String> item_str = Handle<String>::cast(item);
|
Handle<String> item_str = Handle<String>::cast(item);
|
||||||
if (!item_str->IsFlat()) item_str = String::Flatten(isolate, item_str);
|
if (!item_str->IsFlat()) item_str = String::Flatten(isolate, item_str);
|
||||||
result.push_back(Intl::ToICUUnicodeString(isolate, item_str));
|
result.push_back(Intl::ToICUUnicodeString(isolate, item_str));
|
||||||
@ -294,9 +277,6 @@ MaybeHandle<T> FormatListCommon(
|
|||||||
Isolate* isolate, Handle<JSListFormat> format, Handle<JSArray> list,
|
Isolate* isolate, Handle<JSListFormat> format, Handle<JSArray> list,
|
||||||
MaybeHandle<T> (*formatToResult)(Isolate*, const icu::FormattedValue&)) {
|
MaybeHandle<T> (*formatToResult)(Isolate*, const icu::FormattedValue&)) {
|
||||||
DCHECK(!list->IsUndefined());
|
DCHECK(!list->IsUndefined());
|
||||||
// ecma402 #sec-createpartsfromlist
|
|
||||||
// 2. If list contains any element value such that Type(value) is not String,
|
|
||||||
// throw a TypeError exception.
|
|
||||||
Maybe<std::vector<icu::UnicodeString>> maybe_array =
|
Maybe<std::vector<icu::UnicodeString>> maybe_array =
|
||||||
ToUnicodeStringArray(isolate, list);
|
ToUnicodeStringArray(isolate, list);
|
||||||
MAYBE_RETURN(maybe_array, Handle<T>());
|
MAYBE_RETURN(maybe_array, Handle<T>());
|
||||||
|
50
test/intl/regress-9747.js
Normal file
50
test/intl/regress-9747.js
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
// Copyright 2019 the V8 project authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
let lf = new Intl.ListFormat("en");
|
||||||
|
|
||||||
|
// Test normal array
|
||||||
|
assertDoesNotThrow(() => lf.format(['a','b','c']));
|
||||||
|
assertThrows("lf.format(['a','b',3])", TypeError, "Iterable yielded 3 which is not a string");
|
||||||
|
|
||||||
|
// Test sparse array
|
||||||
|
let sparse = ['a','b'];
|
||||||
|
sparse[10] = 'c';
|
||||||
|
assertThrows("lf.format(sparse)", TypeError, "Iterable yielded undefined which is not a string");
|
||||||
|
|
||||||
|
// Test iterable of all String
|
||||||
|
let iterable_of_strings = {
|
||||||
|
[Symbol.iterator]() {
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
count: 0,
|
||||||
|
next() {
|
||||||
|
if (this.count++ < 4) {
|
||||||
|
return {done: false, value: String(this.count)};
|
||||||
|
}
|
||||||
|
return {done:true}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
assertDoesNotThrow(() => lf.format(iterable_of_strings));
|
||||||
|
|
||||||
|
// Test iterable of none String throw TypeError
|
||||||
|
let iterable_of_strings_and_number = {
|
||||||
|
[Symbol.iterator]() {
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
count: 0,
|
||||||
|
next() {
|
||||||
|
this.count++;
|
||||||
|
if (this.count == 3) {
|
||||||
|
return {done:false, value: 3};
|
||||||
|
}
|
||||||
|
if (this.count < 5) {
|
||||||
|
return {done: false, value: String(this.count)};
|
||||||
|
}
|
||||||
|
return {done:true}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
assertThrows("lf.format(iterable_of_strings_and_number)",
|
||||||
|
TypeError, "Iterable yielded 3 which is not a string");
|
||||||
|
assertEquals(3, iterable_of_strings_and_number.count);
|
Loading…
Reference in New Issue
Block a user