Bug 1781730 - Support optional arguments in async iterable declarations. r=edgar

Differential Revision: https://phabricator.services.mozilla.com/D155422
This commit is contained in:
Peter Van der Beken 2022-09-05 15:27:11 +00:00
parent 79ce0456b5
commit d81d165c25
10 changed files with 345 additions and 46 deletions

View File

@ -9519,6 +9519,7 @@ class CGPerSignatureCall(CGThing):
descriptor,
idlNode.maplikeOrSetlikeOrIterable,
idlNode.identifier.name,
self.getArgumentNames(),
)
)
elif idlNode.isAttr() and idlNode.type.isObservableArray():
@ -9602,8 +9603,11 @@ class CGPerSignatureCall(CGThing):
self.cgRoot = CGList(cgThings)
def getArgumentNames(self):
return ["arg" + str(i) for i in range(len(self.arguments))]
def getArguments(self):
return [(a, "arg" + str(i)) for i, a in enumerate(self.arguments)]
return list(zip(self.arguments, self.getArgumentNames()))
def processWebExtensionStubAttribute(self, idlNode, cgThings):
nativeMethodName = "CallWebExtMethod"
@ -22107,8 +22111,10 @@ class CGIterableMethodGenerator(CGGeneric):
using CGCallGenerator.
"""
def __init__(self, descriptor, iterable, methodName):
def __init__(self, descriptor, iterable, methodName, args):
if methodName == "forEach":
assert len(args) == 2
CGGeneric.__init__(
self,
fill(
@ -22144,6 +22150,8 @@ class CGIterableMethodGenerator(CGGeneric):
return
if descriptor.interface.isIterable():
assert len(args) == 0
binding = descriptor.interface.identifier.name + "Iterator_Binding"
init = ""
else:
@ -22154,12 +22162,13 @@ class CGIterableMethodGenerator(CGGeneric):
"""
{
ErrorResult initError;
self->InitAsyncIterator(result.get(), initError);
self->InitAsyncIterator(result.get(), ${args}initError);
if (initError.MaybeSetPendingException(cx, "Asynchronous iterator initialization steps for ${ifaceName} failed")) {
return false;
}
}
""",
args="".join(a + ", " for a in args),
ifaceName=descriptor.interface.identifier.name,
)
CGGeneric.__init__(

View File

@ -132,6 +132,7 @@ if CONFIG["MOZ_DEBUG"] and CONFIG["ENABLE_TESTS"]:
"test/TestInterfaceAsyncIterableDouble.h",
"test/TestInterfaceAsyncIterableDoubleUnion.h",
"test/TestInterfaceAsyncIterableSingle.h",
"test/TestInterfaceAsyncIterableSingleWithArgs.h",
"test/TestInterfaceIterableDouble.h",
"test/TestInterfaceIterableDoubleUnion.h",
"test/TestInterfaceIterableSingle.h",
@ -150,6 +151,7 @@ if CONFIG["MOZ_DEBUG"] and CONFIG["ENABLE_TESTS"]:
"test/TestInterfaceAsyncIterableDouble.cpp",
"test/TestInterfaceAsyncIterableDoubleUnion.cpp",
"test/TestInterfaceAsyncIterableSingle.cpp",
"test/TestInterfaceAsyncIterableSingleWithArgs.cpp",
"test/TestInterfaceIterableDouble.cpp",
"test/TestInterfaceIterableDoubleUnion.cpp",
"test/TestInterfaceIterableSingle.cpp",

View File

@ -13,6 +13,7 @@ import math
import string
from collections import defaultdict, OrderedDict
from itertools import chain
import copy
# Machinery
@ -4723,7 +4724,7 @@ class IDLMaplikeOrSetlikeOrIterableBase(IDLInterfaceMember):
# Iterable adds ES6 iterator style functions and traits
# (keys/values/entries/@@iterator) to an interface.
class IDLIterable(IDLMaplikeOrSetlikeOrIterableBase):
def __init__(self, location, identifier, keyType, valueType=None, scope=None):
def __init__(self, location, identifier, keyType, valueType, scope):
IDLMaplikeOrSetlikeOrIterableBase.__init__(
self,
location,
@ -4798,9 +4799,15 @@ class IDLIterable(IDLMaplikeOrSetlikeOrIterableBase):
class IDLAsyncIterable(IDLMaplikeOrSetlikeOrIterableBase):
def __init__(
self, location, identifier, keyType, valueType=None, argList=None, scope=None
):
def __init__(self, location, identifier, keyType, valueType, argList, scope):
for arg in argList:
if not arg.optional:
raise WebIDLError(
"The arguments of the asynchronously iterable declaration on "
"%s must all be optional arguments." % identifier,
[arg.location],
)
IDLMaplikeOrSetlikeOrIterableBase.__init__(
self,
location,
@ -4812,10 +4819,6 @@ class IDLAsyncIterable(IDLMaplikeOrSetlikeOrIterableBase):
)
self.iteratorType = None
self.argList = argList
if argList is not None:
raise WebIDLError(
"Arguments of async iterable are not supported yet. Please reference Bug 1781730."
)
def __str__(self):
return "declared async iterable with key '%s' and value '%s'" % (
@ -4835,6 +4838,7 @@ class IDLAsyncIterable(IDLMaplikeOrSetlikeOrIterableBase):
members,
False,
self.iteratorType,
self.argList,
affectsNothing=True,
newObject=True,
isIteratorAlias=(not self.isPairIterator()),
@ -4844,12 +4848,17 @@ class IDLAsyncIterable(IDLMaplikeOrSetlikeOrIterableBase):
if not self.isPairIterator():
return
# Methods can't share their IDLArguments, so we need to make copies here.
def copyArgList(argList):
return map(copy.copy, argList)
# object entries()
self.addMethod(
"entries",
members,
False,
self.iteratorType,
copyArgList(self.argList),
affectsNothing=True,
newObject=True,
isIteratorAlias=True,
@ -4860,6 +4869,7 @@ class IDLAsyncIterable(IDLMaplikeOrSetlikeOrIterableBase):
members,
False,
self.iteratorType,
copyArgList(self.argList),
affectsNothing=True,
newObject=True,
)
@ -7753,11 +7763,11 @@ class Parser(Tokenizer):
elif len(p) == 9:
keyType = p[4]
valueType = p[6]
argList = None
argList = []
else:
keyType = None
valueType = p[4]
argList = None
argList = []
p[0] = IDLAsyncIterable(
location, identifier, keyType, valueType, argList, self.globalScope()

View File

@ -89,10 +89,18 @@ def WebIDLTest(parser, harness):
# __iterable to it for the iterable<> case.
iterableMembers.append(("__iterable", WebIDL.IDLIterable))
asyncIterableMembers = [
(x, WebIDL.IDLMethod) for x in ["entries", "keys", "values"]
]
asyncIterableMembers.append(("__iterable", WebIDL.IDLAsyncIterable))
valueIterableMembers = [("__iterable", WebIDL.IDLIterable)]
valueIterableMembers.append(("__indexedgetter", WebIDL.IDLMethod))
valueIterableMembers.append(("length", WebIDL.IDLAttribute))
valueAsyncIterableMembers = [("__iterable", WebIDL.IDLAsyncIterable)]
valueAsyncIterableMembers.append(("values", WebIDL.IDLMethod))
disallowedIterableNames = ["keys", "entries", "values"]
disallowedMemberNames = ["forEach", "has", "size"] + disallowedIterableNames
mapDisallowedMemberNames = ["get"] + disallowedMemberNames
@ -169,6 +177,94 @@ def WebIDLTest(parser, harness):
numProductions=3,
)
shouldPass(
"Async iterable (key only)",
"""
interface Foo1 {
async iterable<long>;
attribute long unrelatedAttribute;
long unrelatedMethod();
};
""",
valueAsyncIterableMembers + unrelatedMembers,
# numProductions == 2 because of the generated iterator iface,
numProductions=2,
)
shouldPass(
"Async iterable (key only) inheriting from parent",
"""
interface Foo1 : Foo2 {
async iterable<long>;
};
interface Foo2 {
attribute long unrelatedAttribute;
long unrelatedMethod();
};
""",
valueAsyncIterableMembers,
# numProductions == 3 because of the generated iterator iface,
numProductions=3,
)
shouldPass(
"Async iterable with argument (key only)",
"""
interface Foo1 {
async iterable<long>(optional long foo);
attribute long unrelatedAttribute;
long unrelatedMethod();
};
""",
valueAsyncIterableMembers + unrelatedMembers,
# numProductions == 2 because of the generated iterator iface,
numProductions=2,
)
shouldPass(
"Async iterable (key and value)",
"""
interface Foo1 {
async iterable<long, long>;
attribute long unrelatedAttribute;
long unrelatedMethod();
};
""",
asyncIterableMembers + unrelatedMembers,
# numProductions == 2 because of the generated iterator iface,
numProductions=2,
)
shouldPass(
"Async iterable (key and value) inheriting from parent",
"""
interface Foo1 : Foo2 {
async iterable<long, long>;
};
interface Foo2 {
attribute long unrelatedAttribute;
long unrelatedMethod();
};
""",
asyncIterableMembers,
# numProductions == 3 because of the generated iterator iface,
numProductions=3,
)
shouldPass(
"Async iterable with argument (key and value)",
"""
interface Foo1 {
async iterable<long, long>(optional long foo);
attribute long unrelatedAttribute;
long unrelatedMethod();
};
""",
asyncIterableMembers + unrelatedMembers,
# numProductions == 2 because of the generated iterator iface,
numProductions=2,
)
shouldPass(
"Maplike (readwrite)",
"""
@ -373,6 +469,53 @@ def WebIDLTest(parser, harness):
""",
)
shouldFail(
"Two iterables on same interface",
"""
interface Foo1 {
iterable<long>;
async iterable<long>;
};
""",
)
shouldFail(
"Two iterables on same interface",
"""
interface Foo1 {
async iterable<long>;
async iterable<long, long>;
};
""",
)
shouldFail(
"Async iterable with non-optional arguments",
"""
interface Foo1 {
async iterable<long>(long foo);
};
""",
)
shouldFail(
"Async iterable with non-optional arguments",
"""
interface Foo1 {
async iterable<long>(optional long foo, long bar);
};
""",
)
shouldFail(
"Async iterable with non-optional arguments",
"""
interface Foo1 {
async iterable<long, long>(long foo);
};
""",
)
shouldFail(
"Two maplike/setlikes in partials",
"""

View File

@ -59,7 +59,7 @@ void TestInterfaceAsyncIterableSingle::InitAsyncIterator(Iterator* aIterator,
return;
}
UniquePtr<IteratorData> data(new IteratorData(0));
UniquePtr<IteratorData> data(new IteratorData(0, 1));
aIterator->SetData((void*)data.release());
}
@ -71,21 +71,31 @@ void TestInterfaceAsyncIterableSingle::DestroyAsyncIterator(
already_AddRefed<Promise> TestInterfaceAsyncIterableSingle::GetNextPromise(
JSContext* aCx, Iterator* aIterator, ErrorResult& aRv) {
return GetNextPromise(aCx, aIterator,
reinterpret_cast<IteratorData*>(aIterator->GetData()),
aRv);
}
already_AddRefed<Promise> TestInterfaceAsyncIterableSingle::GetNextPromise(
JSContext* aCx, IterableIteratorBase* aIterator, IteratorData* aData,
ErrorResult& aRv) {
RefPtr<Promise> promise = Promise::Create(mParent->AsGlobal(), aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
auto* data = reinterpret_cast<IteratorData*>(aIterator->GetData());
data->mPromise = promise;
aData->mPromise = promise;
NS_DispatchToMainThread(NS_NewRunnableFunction(
"TestInterfaceAsyncIterableSingle::GetNextPromise",
[data, self = RefPtr{this}] { self->ResolvePromise(data); }));
[iterator = RefPtr{aIterator}, aData, self = RefPtr{this}] {
self->ResolvePromise(iterator, aData);
}));
return promise.forget();
}
void TestInterfaceAsyncIterableSingle::ResolvePromise(IteratorData* aData) {
void TestInterfaceAsyncIterableSingle::ResolvePromise(
IterableIteratorBase* aIterator, IteratorData* aData) {
AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.Init(mParent))) {
return;
@ -96,7 +106,8 @@ void TestInterfaceAsyncIterableSingle::ResolvePromise(IteratorData* aData) {
iterator_utils::ResolvePromiseForFinished(cx, aData->mPromise, rv);
} else {
JS::Rooted<JS::Value> value(cx);
Unused << ToJSValue(cx, (int32_t)(aData->mIndex * 9 % 7), &value);
Unused << ToJSValue(
cx, (int32_t)(aData->mIndex * 9 % 7 * aData->mMultiplier), &value);
iterator_utils::ResolvePromiseWithKeyOrValue(cx, aData->mPromise, value,
rv);

View File

@ -25,14 +25,14 @@ struct TestInterfaceAsyncIterableSingleOptions;
// Implementation of test binding for webidl iterable interfaces, using
// primitives for value type
class TestInterfaceAsyncIterableSingle final : public nsISupports,
public nsWrapperCache {
class TestInterfaceAsyncIterableSingle : public nsISupports,
public nsWrapperCache {
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(TestInterfaceAsyncIterableSingle)
TestInterfaceAsyncIterableSingle(nsPIDOMWindowInner* aParent,
bool aFailToInit);
explicit TestInterfaceAsyncIterableSingle(nsPIDOMWindowInner* aParent,
bool aFailToInit = false);
nsPIDOMWindowInner* GetParentObject() const;
virtual JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
@ -46,9 +46,10 @@ class TestInterfaceAsyncIterableSingle final : public nsISupports,
already_AddRefed<Promise> GetNextPromise(JSContext* aCx, Iterator* aIterator,
ErrorResult& aRv);
private:
protected:
struct IteratorData {
explicit IteratorData(int32_t aIndex) : mIndex(aIndex) {}
IteratorData(int32_t aIndex, uint32_t aMultiplier)
: mIndex(aIndex), mMultiplier(aMultiplier) {}
~IteratorData() {
if (mPromise) {
mPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
@ -57,9 +58,18 @@ class TestInterfaceAsyncIterableSingle final : public nsISupports,
}
RefPtr<Promise> mPromise;
uint32_t mIndex;
uint32_t mMultiplier;
};
already_AddRefed<Promise> GetNextPromise(JSContext* aCx,
IterableIteratorBase* aIterator,
IteratorData* aData,
ErrorResult& aRv);
virtual ~TestInterfaceAsyncIterableSingle() = default;
void ResolvePromise(IteratorData* aData);
private:
void ResolvePromise(IterableIteratorBase* aIterator, IteratorData* aData);
nsCOMPtr<nsPIDOMWindowInner> mParent;
bool mFailToInit;

View File

@ -0,0 +1,59 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/TestInterfaceAsyncIterableSingleWithArgs.h"
#include "mozilla/dom/TestInterfaceJSMaplikeSetlikeIterableBinding.h"
#include "nsPIDOMWindow.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/IterableIterator.h"
namespace mozilla::dom {
// static
already_AddRefed<TestInterfaceAsyncIterableSingleWithArgs>
TestInterfaceAsyncIterableSingleWithArgs::Constructor(
const GlobalObject& aGlobal, ErrorResult& aRv) {
nsCOMPtr<nsPIDOMWindowInner> window =
do_QueryInterface(aGlobal.GetAsSupports());
if (!window) {
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
RefPtr<TestInterfaceAsyncIterableSingleWithArgs> r =
new TestInterfaceAsyncIterableSingleWithArgs(window);
return r.forget();
}
JSObject* TestInterfaceAsyncIterableSingleWithArgs::WrapObject(
JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
return TestInterfaceAsyncIterableSingleWithArgs_Binding::Wrap(aCx, this,
aGivenProto);
}
void TestInterfaceAsyncIterableSingleWithArgs::InitAsyncIterator(
Iterator* aIterator, const TestInterfaceAsyncIteratorOptions& aOptions,
ErrorResult& aError) {
UniquePtr<IteratorData> data(new IteratorData(0, aOptions.mMultiplier));
aIterator->SetData((void*)data.release());
}
void TestInterfaceAsyncIterableSingleWithArgs::DestroyAsyncIterator(
Iterator* aIterator) {
auto* data = reinterpret_cast<IteratorData*>(aIterator->GetData());
delete data;
}
already_AddRefed<Promise>
TestInterfaceAsyncIterableSingleWithArgs::GetNextPromise(JSContext* aCx,
Iterator* aIterator,
ErrorResult& aRv) {
return TestInterfaceAsyncIterableSingle::GetNextPromise(
aCx, aIterator, reinterpret_cast<IteratorData*>(aIterator->GetData()),
aRv);
}
} // namespace mozilla::dom

View File

@ -0,0 +1,40 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_TestInterfaceAsyncIterableSingleWithArgs_h
#define mozilla_dom_TestInterfaceAsyncIterableSingleWithArgs_h
#include "mozilla/dom/TestInterfaceAsyncIterableSingle.h"
namespace mozilla::dom {
struct TestInterfaceAsyncIteratorOptions;
// Implementation of test binding for webidl iterable interfaces, using
// primitives for value type
class TestInterfaceAsyncIterableSingleWithArgs final
: public TestInterfaceAsyncIterableSingle {
public:
using TestInterfaceAsyncIterableSingle::TestInterfaceAsyncIterableSingle;
virtual JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
static already_AddRefed<TestInterfaceAsyncIterableSingleWithArgs> Constructor(
const GlobalObject& aGlobal, ErrorResult& rv);
using Iterator =
AsyncIterableIterator<TestInterfaceAsyncIterableSingleWithArgs>;
void InitAsyncIterator(Iterator* aIterator,
const TestInterfaceAsyncIteratorOptions& aOptions,
ErrorResult& aError);
void DestroyAsyncIterator(Iterator* aIterator);
already_AddRefed<Promise> GetNextPromise(JSContext* aCx, Iterator* aIterator,
ErrorResult& aRv);
};
} // namespace mozilla::dom
#endif // mozilla_dom_TestInterfaceAsyncIterableSingleWithArgs_h

View File

@ -14,6 +14,19 @@ add_task(async function init() {
await SpecialPowers.pushPrefEnv({set: [["dom.expose_test_interfaces", true]]});
});
async function check_single_result(itr, multiplier = 1) {
let values = [];
for await (let v of itr) {
values.push(v);
}
is(values.length, 10, `AsyncIterableSingle: should returns 10 elements`);
for (let i = 0; i < 10; i++) {
let expected = i * 9 % 7 * multiplier;
is(values[i], expected,
`AsyncIterableSingle: should be ${expected}, get ${values[i]}`);
}
}
async function test_data_single() {
info(`AsyncIterableSingle: Testing simple iterable creation and functionality`);
@ -35,27 +48,16 @@ async function test_data_single() {
is(itr.values, itr[Symbol.asyncIterator],
`AsyncIterableSingle: Should be using @@asyncIterator for 'values'`);
let values = [];
for await (let v of itr) {
values.push(v);
}
is(values.length, 10, `AsyncIterableSingle: should returns 10 elements`);
for (let i = 0; i < 10; i++) {
let expected = i * 9 % 7;
is(values[i], i * 9 % 7,
`AsyncIterableSingle: should be ${expected}, get ${values[i]}`);
}
await check_single_result(itr);
await check_single_result(itr.values());
values = [];
for await (let v of itr.values()) {
values.push(v);
}
is(values.length, 10, `AsyncIterableSingle: should returns 10 elements`);
for (let i = 0; i < 10; i++) {
let expected = i * 9 % 7;
is(values[i], i * 9 % 7,
`AsyncIterableSingle: should be ${expected}, get ${values[i]}`);
}
// eslint-disable-next-line no-undef
itr = new TestInterfaceAsyncIterableSingleWithArgs();
is(itr.values, itr[Symbol.asyncIterator],
`AsyncIterableSingleWithArgs: Should be using @@asyncIterator for 'values'`);
await check_single_result(itr, 1);
await check_single_result(itr.values({ multiplier: 2 }), 2);
}
async function test_data_double() {

View File

@ -109,6 +109,19 @@ interface TestInterfaceAsyncIterableSingle {
async iterable<long>;
};
dictionary TestInterfaceAsyncIteratorOptions {
unsigned long multiplier = 1;
};
[Pref="dom.expose_test_interfaces",
Exposed=Window]
interface TestInterfaceAsyncIterableSingleWithArgs {
[Throws]
constructor();
async iterable<long>(optional TestInterfaceAsyncIteratorOptions options = {});
};
[Pref="dom.expose_test_interfaces",
Exposed=Window]
interface TestInterfaceAsyncIterableDouble {