#488: basic support for DOM iterables, {NodeList,DOMTokenList}.forEach M1216751 M1290636

This commit is contained in:
Cameron Kaiser 2018-03-20 18:06:26 -07:00
parent 83c11df480
commit 9470d4fb94
6 changed files with 254 additions and 229 deletions

View File

@ -1136,11 +1136,13 @@ class CGHeaders(CGWrapper):
if desc.interface.maplikeOrSetlikeOrIterable:
# We need ToJSValue.h for maplike/setlike type conversions
bindingHeaders.add("mozilla/dom/ToJSValue.h")
# Add headers for the key and value types of the maplike, since
# they'll be needed for convenience functions
addHeadersForType((desc.interface.maplikeOrSetlikeOrIterable.keyType,
desc, None))
if desc.interface.maplikeOrSetlikeOrIterable.valueType:
# Add headers for the key and value types of the
# maplike/setlike/iterable, since they'll be needed for
# convenience functions
if desc.interface.maplikeOrSetlikeOrIterable.hasKeyType():
addHeadersForType((desc.interface.maplikeOrSetlikeOrIterable.keyType,
desc, None))
if desc.interface.maplikeOrSetlikeOrIterable.hasValueType():
addHeadersForType((desc.interface.maplikeOrSetlikeOrIterable.valueType,
desc, None))
@ -2269,34 +2271,50 @@ class MethodDefiner(PropertyDefiner):
"condition": MemberCondition()
})
# Generate the maplike/setlike iterator, if one wasn't already
# generated by a method. If we already have an @@iterator symbol, fail.
if descriptor.interface.maplikeOrSetlikeOrIterable:
if hasIterator(methods, self.regular):
raise TypeError("Cannot have maplike/setlike/iterable interface with "
"other members that generate @@iterator "
"on interface %s, such as indexed getters "
"or aliased functions." %
self.descriptor.interface.identifier.name)
for m in methods:
if (m.isMaplikeOrSetlikeOrIterableMethod() and
(((m.maplikeOrSetlikeOrIterable.isMaplike() or
(m.maplikeOrSetlikeOrIterable.isIterable() and
m.maplikeOrSetlikeOrIterable.hasValueType())) and
m.identifier.name == "entries") or
(((m.maplikeOrSetlikeOrIterable.isSetlike() or
(m.maplikeOrSetlikeOrIterable.isIterable() and
not m.maplikeOrSetlikeOrIterable.hasValueType()))) and
m.identifier.name == "values"))):
self.regular.append({
"name": "@@iterator",
"methodName": m.identifier.name,
"length": methodLength(m),
"flags": "0",
"condition": PropertyDefiner.getControllingCondition(m,
descriptor),
})
break
# Generate the keys/values/entries aliases for value iterables.
maplikeOrSetlikeOrIterable = descriptor.interface.maplikeOrSetlikeOrIterable
if (not static and
not unforgeable and
maplikeOrSetlikeOrIterable and
maplikeOrSetlikeOrIterable.isIterable() and
maplikeOrSetlikeOrIterable.isValueIterator()):
# Add our keys/values/entries/forEach
self.regular.append({
"name": "keys",
"methodInfo": False,
"selfHostedName": "ArrayKeys",
"length": 0,
"flags": "JSPROP_ENUMERATE",
"condition": PropertyDefiner.getControllingCondition(m,
descriptor)
})
self.regular.append({
"name": "values",
"methodInfo": False,
"selfHostedName": "ArrayValues",
"length": 0,
"flags": "JSPROP_ENUMERATE",
"condition": PropertyDefiner.getControllingCondition(m,
descriptor)
})
self.regular.append({
"name": "entries",
"methodInfo": False,
"selfHostedName": "ArrayEntries",
"length": 0,
"flags": "JSPROP_ENUMERATE",
"condition": PropertyDefiner.getControllingCondition(m,
descriptor)
})
self.regular.append({
"name": "forEach",
"methodInfo": False,
"selfHostedName": "ArrayForEach",
"length": 1,
"flags": "JSPROP_ENUMERATE",
"condition": PropertyDefiner.getControllingCondition(m,
descriptor)
})
if not static:
stringifier = descriptor.operations['Stringifier']
@ -13008,8 +13026,9 @@ class CGForwardDeclarations(CGWrapper):
# arguments to helper functions, and they'll need to be forward
# declared in the header.
if d.interface.maplikeOrSetlikeOrIterable:
builder.forwardDeclareForType(d.interface.maplikeOrSetlikeOrIterable.keyType,
config)
if d.interface.maplikeOrSetlikeOrIterable.hasKeyType():
builder.forwardDeclareForType(d.interface.maplikeOrSetlikeOrIterable.keyType,
config)
if d.interface.maplikeOrSetlikeOrIterable.hasValueType():
builder.forwardDeclareForType(d.interface.maplikeOrSetlikeOrIterable.valueType,
config)
@ -15735,6 +15754,31 @@ class CGIterableMethodGenerator(CGGeneric):
using CGCallGenerator.
"""
def __init__(self, descriptor, iterable, methodName):
if methodName == "forEach":
CGGeneric.__init__(self, fill(
"""
if (!JS::IsCallable(arg0)) {
ThrowErrorMessage(cx, MSG_NOT_CALLABLE, "Argument 1 of ${ifaceName}.forEach");
return false;
}
JS::AutoValueArray<3> callArgs(cx);
callArgs[2].setObject(*obj);
JS::Rooted<JS::Value> ignoredReturnVal(cx);
for (size_t i = 0; i < self->GetIterableLength(); ++i) {
if (!ToJSValue(cx, self->GetValueAtIndex(i), callArgs[0])) {
return false;
}
if (!ToJSValue(cx, self->GetKeyAtIndex(i), callArgs[1])) {
return false;
}
if (!JS::Call(cx, arg1, arg0, JS::HandleValueArray(callArgs),
&ignoredReturnVal)) {
return false;
}
}
""",
ifaceName=descriptor.interface.identifier.name))
return
CGGeneric.__init__(self, fill(
"""
typedef ${iterClass} itrType;

View File

@ -893,8 +893,5 @@ def getAllTypes(descriptors, dictionaries, callbacks):
def iteratorNativeType(descriptor):
assert descriptor.interface.isIterable()
iterableDecl = descriptor.interface.maplikeOrSetlikeOrIterable
if iterableDecl.valueType is None:
iterClass = "OneTypeIterableIterator"
else:
iterClass = "TwoTypeIterableIterator"
return "mozilla::dom::%s<%s>" % (iterClass, descriptor.nativeType)
assert iterableDecl.isPairIterator()
return "mozilla::dom::IterableIterator<%s>" % descriptor.nativeType

View File

@ -6,19 +6,17 @@
/**
* The IterableIterator class is used for WebIDL interfaces that have a
* iterable<> member defined. It handles the ES6 Iterator-like functions that
* are generated for the iterable interface.
* iterable<> member defined with two types (so a pair iterator). It handles
* the ES6 Iterator-like functions that are generated for the iterable
* interface.
*
* For iterable interfaces, the implementation class will need to
* implement these two functions:
* For iterable interfaces with a pair iterator, the implementation class will
* need to implement these two functions:
*
* - size_t GetIterableLength()
* - Returns the number of elements available to iterate over
* - [type] GetValueAtIndex(size_t index)
* - Returns the value at the requested index.
*
* If this is a two-type iterator, then the implementation class will also need to implement:
*
* - [type] GetKeyAtIndex(size_t index)
* - Returns the key at the requested index
*
@ -60,13 +58,77 @@ protected:
};
template <typename T>
class IterableIterator : public IterableIteratorBase
class IterableIterator final : public IterableIteratorBase
{
public:
explicit IterableIterator(T* aIterableObj)
typedef bool (*WrapFunc)(JSContext* aCx,
IterableIterator<T>* aObject,
JS::Handle<JSObject*> aGivenProto,
JS::MutableHandle<JSObject*> aReflector);
explicit IterableIterator(T* aIterableObj,
IterableIteratorType aIteratorType,
WrapFunc aWrapFunc)
: mIterableObj(aIterableObj)
, mIteratorType(aIteratorType)
, mWrapFunc(aWrapFunc)
, mIndex(0)
{
MOZ_ASSERT(mIterableObj);
MOZ_ASSERT(mWrapFunc);
}
void
Next(JSContext* aCx, JS::MutableHandle<JSObject*> aResult, ErrorResult& aRv)
{
JS::Rooted<JS::Value> value(aCx, JS::UndefinedValue());
if (mIndex >= this->mIterableObj->GetIterableLength()) {
DictReturn(aCx, aResult, true, value, aRv);
return;
}
switch (mIteratorType) {
case IterableIteratorType::Keys:
{
if (!ToJSValue(aCx, this->mIterableObj->GetKeyAtIndex(mIndex), &value)) {
aRv.Throw(NS_ERROR_FAILURE);
return;
}
DictReturn(aCx, aResult, false, value, aRv);
break;
}
case IterableIteratorType::Values:
{
if (!ToJSValue(aCx, this->mIterableObj->GetValueAtIndex(mIndex), &value)) {
aRv.Throw(NS_ERROR_FAILURE);
return;
}
DictReturn(aCx, aResult, false, value, aRv);
break;
}
case IterableIteratorType::Entries:
{
JS::Rooted<JS::Value> key(aCx);
if (!ToJSValue(aCx, this->mIterableObj->GetKeyAtIndex(mIndex), &key)) {
aRv.Throw(NS_ERROR_FAILURE);
return;
}
if (!ToJSValue(aCx, this->mIterableObj->GetValueAtIndex(mIndex), &value)) {
aRv.Throw(NS_ERROR_FAILURE);
return;
}
KeyAndValueReturn(aCx, key, value, aResult, aRv);
break;
}
default:
MOZ_CRASH("Invalid iterator type!");
}
++mIndex;
}
bool
WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto, JS::MutableHandle<JSObject*> aObj)
{
return (*mWrapFunc)(aCx, this, aGivenProto, aObj);
}
protected:
@ -128,161 +190,6 @@ protected:
// Binding Implementation object that we're iterating over.
RefPtr<T> mIterableObj;
};
template<typename T>
class OneTypeIterableIterator final : public IterableIterator<T>
{
public:
typedef typename IterableIterator<T>::IterableIteratorType IterableIteratorType;
using IterableIterator<T>::DictReturn;
using IterableIterator<T>::KeyAndValueReturn;
typedef bool (*WrapFunc)(JSContext* aCx,
OneTypeIterableIterator<T>* aObject,
JS::Handle<JSObject*> aGivenProto,
JS::MutableHandle<JSObject*> aReflector);
OneTypeIterableIterator(T* aIterableObj,
IterableIteratorType aIteratorType,
WrapFunc aWrapFunc)
: IterableIterator<T>(aIterableObj)
, mIteratorType(aIteratorType)
, mWrapFunc(aWrapFunc)
, mIndex(0)
{
MOZ_ASSERT(mWrapFunc);
}
void
Next(JSContext* aCx, JS::MutableHandle<JSObject*> aResult, ErrorResult& aRv)
{
JS::Rooted<JS::Value> value(aCx, JS::UndefinedValue());
if (mIndex >= this->mIterableObj->GetIterableLength()) {
DictReturn(aCx, aResult, true, value, aRv);
return;
}
switch (mIteratorType) {
case IterableIteratorType::Keys:
case IterableIteratorType::Values:
{
if (!ToJSValue(aCx, this->mIterableObj->GetValueAtIndex(mIndex), &value)) {
aRv.Throw(NS_ERROR_FAILURE);
return;
}
DictReturn(aCx, aResult, false, value, aRv);
break;
}
case IterableIteratorType::Entries:
{
if (!ToJSValue(aCx, this->mIterableObj->GetValueAtIndex(mIndex), &value)) {
aRv.Throw(NS_ERROR_FAILURE);
return;
}
KeyAndValueReturn(aCx, value, value, aResult, aRv);
break;
}
default:
MOZ_CRASH("Invalid iterator type!");
}
++mIndex;
}
bool
WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto, JS::MutableHandle<JSObject*> aObj)
{
return (*mWrapFunc)(aCx, this, aGivenProto, aObj);
}
protected:
virtual ~OneTypeIterableIterator() {}
// Tells whether this is a key, value, or entries iterator.
IterableIteratorType mIteratorType;
// Function pointer to binding-type-specific Wrap() call for this iterator.
WrapFunc mWrapFunc;
// Current index of iteration.
uint32_t mIndex;
};
template<typename T>
class TwoTypeIterableIterator final : public IterableIterator<T>
{
public:
typedef typename IterableIterator<T>::IterableIteratorType IterableIteratorType;
using IterableIterator<T>::DictReturn;
using IterableIterator<T>::KeyAndValueReturn;
typedef bool (*WrapFunc)(JSContext* aCx,
TwoTypeIterableIterator<T>* aObject,
JS::Handle<JSObject*> aGivenProto,
JS::MutableHandle<JSObject*> aReflector);
TwoTypeIterableIterator(T* aIterableObj, IterableIteratorType aIteratorType,
WrapFunc aWrapFunc)
: IterableIterator<T>(aIterableObj)
, mIteratorType(aIteratorType)
, mWrapFunc(aWrapFunc)
, mIndex(0)
{
MOZ_ASSERT(mWrapFunc);
}
void
Next(JSContext* aCx, JS::MutableHandle<JSObject*> aResult, ErrorResult& aRv)
{
JS::Rooted<JS::Value> value(aCx, JS::UndefinedValue());
if (mIndex >= this->mIterableObj->GetIterableLength()) {
DictReturn(aCx, aResult, true, value, aRv);
return;
}
switch (mIteratorType) {
case IterableIteratorType::Keys:
{
if (!ToJSValue(aCx, this->mIterableObj->GetKeyAtIndex(mIndex), &value)) {
aRv.Throw(NS_ERROR_FAILURE);
return;
}
DictReturn(aCx, aResult, false, value, aRv);
break;
}
case IterableIteratorType::Values:
{
if (!ToJSValue(aCx, this->mIterableObj->GetValueAtIndex(mIndex), &value)) {
aRv.Throw(NS_ERROR_FAILURE);
return;
}
DictReturn(aCx, aResult, false, value, aRv);
break;
}
case IterableIteratorType::Entries:
{
JS::Rooted<JS::Value> key(aCx);
if (!ToJSValue(aCx, this->mIterableObj->GetKeyAtIndex(mIndex), &key)) {
aRv.Throw(NS_ERROR_FAILURE);
return;
}
if (!ToJSValue(aCx, this->mIterableObj->GetValueAtIndex(mIndex), &value)) {
aRv.Throw(NS_ERROR_FAILURE);
return;
}
KeyAndValueReturn(aCx, key, value, aResult, aRv);
break;
}
default:
MOZ_CRASH("Invalid iterator type!");
}
++mIndex;
}
bool
WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto, JS::MutableHandle<JSObject*> aObj)
{
return (*mWrapFunc)(aCx, this, aGivenProto, aObj);
}
protected:
virtual ~TwoTypeIterableIterator() {}
// Tells whether this is a key, value, or entries iterator.
IterableIteratorType mIteratorType;
// Function pointer to binding-type-specific Wrap() call for this iterator.

View File

@ -1089,7 +1089,7 @@ class IDLInterface(IDLObjectWithScope, IDLExposureMixins):
def validate(self):
# We don't support consequential unforgeable interfaces. Need to check
# this here, becaue in finish() an interface might not know yet that
# this here, because in finish() an interface might not know yet that
# it's consequential.
if self.getExtendedAttribute("Unforgeable") and self.isConsequential():
raise WebIDLError(
@ -1108,6 +1108,8 @@ class IDLInterface(IDLObjectWithScope, IDLExposureMixins):
self.identifier.name,
locations)
indexedGetter = None
hasLengthAttribute = False
for member in self.members:
member.validate()
@ -1118,8 +1120,13 @@ class IDLInterface(IDLObjectWithScope, IDLExposureMixins):
[self.location, member.location])
# Check that PutForwards refers to another attribute and that no
# cycles exist in forwarded assignments.
# cycles exist in forwarded assignments. Also check for a
# integer-typed "length" attribute.
if member.isAttr():
if (member.identifier.name == "length" and
member.type.isInteger()):
hasLengthAttribute = True
iface = self
attr = member
putForwards = attr.getExtendedAttribute("PutForwards")
@ -1157,8 +1164,11 @@ class IDLInterface(IDLObjectWithScope, IDLExposureMixins):
putForwards = attr.getExtendedAttribute("PutForwards")
# Check that the name of an [Alias] doesn't conflict with an
# interface member.
# interface member and whether we support indexed properties.
if member.isMethod():
if member.isGetter() and member.isIndexed():
indexedGetter = member
for alias in member.aliases:
if self.isOnGlobalProtoChain():
raise WebIDLError("[Alias] must not be used on a "
@ -1219,6 +1229,35 @@ class IDLInterface(IDLObjectWithScope, IDLExposureMixins):
"exposed conditionally",
[self.location])
# Value iterators are only allowed on interfaces with indexed getters,
# and pair iterators are only allowed on interfaces without indexed
# getters.
if self.isIterable():
iterableDecl = self.maplikeOrSetlikeOrIterable
if iterableDecl.isValueIterator():
if not indexedGetter:
raise WebIDLError("Interface with value iterator does not "
"support indexed properties",
[self.location])
if iterableDecl.valueType != indexedGetter.signatures()[0][0]:
raise WebIDLError("Iterable type does not match indexed "
"getter type",
[iterableDecl.location,
indexedGetter.location])
if not hasLengthAttribute:
raise WebIDLError('Interface with value iterator does not '
'have an integer-typed "length" attribute',
[self.location])
else:
assert iterableDecl.isPairIterator()
if indexedGetter:
raise WebIDLError("Interface with pair iterator supports "
"indexed properties",
[self.location, iterableDecl.location,
indexedGetter.location])
def isInterface(self):
return True
@ -3408,7 +3447,10 @@ class IDLMaplikeOrSetlikeOrIterableBase(IDLInterfaceMember):
def __init__(self, location, identifier, ifaceType, keyType, valueType, ifaceKind):
IDLInterfaceMember.__init__(self, location, identifier, ifaceKind)
assert isinstance(keyType, IDLType)
if keyType is not None:
assert isinstance(keyType, IDLType)
else:
assert valueType is not None
assert ifaceType in ['maplike', 'setlike', 'iterable']
if valueType is not None:
assert isinstance(valueType, IDLType)
@ -3427,6 +3469,9 @@ class IDLMaplikeOrSetlikeOrIterableBase(IDLInterfaceMember):
def isIterable(self):
return self.maplikeOrSetlikeOrIterableType == "iterable"
def hasKeyType(self):
return self.keyType is not None
def hasValueType(self):
return self.valueType is not None
@ -3451,7 +3496,8 @@ class IDLMaplikeOrSetlikeOrIterableBase(IDLInterfaceMember):
[self.location, member.location])
def addMethod(self, name, members, allowExistingOperations, returnType, args=[],
chromeOnly=False, isPure=False, affectsNothing=False, newObject=False):
chromeOnly=False, isPure=False, affectsNothing=False, newObject=False,
isIteratorAlias=False):
"""
Create an IDLMethod based on the parameters passed in.
@ -3511,16 +3557,20 @@ class IDLMaplikeOrSetlikeOrIterableBase(IDLInterfaceMember):
if newObject:
method.addExtendedAttributes(
[IDLExtendedAttribute(self.location, ("NewObject",))])
if isIteratorAlias:
method.addExtendedAttributes(
[IDLExtendedAttribute(self.location, ("Alias", "@@iterator"))])
members.append(method)
def resolve(self, parentScope):
self.keyType.resolveType(parentScope)
if self.keyType:
self.keyType.resolveType(parentScope)
if self.valueType:
self.valueType.resolveType(parentScope)
def finish(self, scope):
IDLInterfaceMember.finish(self, scope)
if not self.keyType.isComplete():
if self.keyType and not self.keyType.isComplete():
t = self.keyType.complete(scope)
assert not isinstance(t, IDLUnresolvedType)
@ -3542,9 +3592,23 @@ class IDLMaplikeOrSetlikeOrIterableBase(IDLInterfaceMember):
IDLInterfaceMember.handleExtendedAttribute(self, attr)
def _getDependentObjects(self):
deps = set()
if self.keyType:
deps.add(self.keyType)
if self.valueType:
return set([self.keyType, self.valueType])
return set([self.keyType])
deps.add(self.valueType)
return deps
def getForEachArguments(self):
return [IDLArgument(self.location,
IDLUnresolvedIdentifier(BuiltinLocation("<auto-generated-identifier>"),
"callback"),
BuiltinTypes[IDLBuiltinType.Types.object]),
IDLArgument(self.location,
IDLUnresolvedIdentifier(BuiltinLocation("<auto-generated-identifier>"),
"thisArg"),
BuiltinTypes[IDLBuiltinType.Types.any],
optional=True)]
# Iterable adds ES6 iterator style functions and traits
# (keys/values/entries/@@iterator) to an interface.
@ -3565,9 +3629,15 @@ class IDLIterable(IDLMaplikeOrSetlikeOrIterableBase):
we generate our functions as if they were part of the interface
specification during parsing.
"""
# We only need to add entries/keys/values here if we're a pair iterator.
# Value iterators just copy these from %ArrayPrototype% instead.
if not self.isPairIterator():
return
# object entries()
self.addMethod("entries", members, False, self.iteratorType,
affectsNothing=True, newObject=True)
affectsNothing=True, newObject=True,
isIteratorAlias=True)
# object keys()
self.addMethod("keys", members, False, self.iteratorType,
affectsNothing=True, newObject=True)
@ -3575,6 +3645,17 @@ class IDLIterable(IDLMaplikeOrSetlikeOrIterableBase):
self.addMethod("values", members, False, self.iteratorType,
affectsNothing=True, newObject=True)
# void forEach(callback(valueType, keyType), optional any thisArg)
self.addMethod("forEach", members, False,
BuiltinTypes[IDLBuiltinType.Types.void],
self.getForEachArguments())
def isValueIterator(self):
return not self.isPairIterator()
def isPairIterator(self):
return self.hasKeyType()
# MaplikeOrSetlike adds ES6 map-or-set-like traits to an interface.
class IDLMaplikeOrSetlike(IDLMaplikeOrSetlikeOrIterableBase):
@ -3611,26 +3692,17 @@ class IDLMaplikeOrSetlike(IDLMaplikeOrSetlikeOrIterableBase):
# object entries()
self.addMethod("entries", members, False, BuiltinTypes[IDLBuiltinType.Types.object],
affectsNothing=True)
affectsNothing=True, isIteratorAlias=self.isMaplike())
# object keys()
self.addMethod("keys", members, False, BuiltinTypes[IDLBuiltinType.Types.object],
affectsNothing=True)
# object values()
self.addMethod("values", members, False, BuiltinTypes[IDLBuiltinType.Types.object],
affectsNothing=True)
affectsNothing=True, isIteratorAlias=self.isSetlike())
# void forEach(callback(valueType, keyType), thisVal)
foreachArguments = [IDLArgument(self.location,
IDLUnresolvedIdentifier(BuiltinLocation("<auto-generated-identifier>"),
"callback"),
BuiltinTypes[IDLBuiltinType.Types.object]),
IDLArgument(self.location,
IDLUnresolvedIdentifier(BuiltinLocation("<auto-generated-identifier>"),
"thisArg"),
BuiltinTypes[IDLBuiltinType.Types.any],
optional=True)]
self.addMethod("forEach", members, False, BuiltinTypes[IDLBuiltinType.Types.void],
foreachArguments)
self.getForEachArguments())
def getKeyArg():
return IDLArgument(self.location,
@ -5436,10 +5508,13 @@ class Parser(Tokenizer):
location = self.getLocation(p, 2)
identifier = IDLUnresolvedIdentifier(location, "__iterable",
allowDoubleUnderscore=True)
keyType = p[3]
valueType = None
if (len(p) > 6):
keyType = p[3]
valueType = p[5]
else:
keyType = None
valueType = p[3]
p[0] = IDLIterable(location, identifier, keyType, valueType, self.globalScope())
def p_Setlike(self, p):
@ -6509,7 +6584,7 @@ class Parser(Tokenizer):
if isinstance(m, IDLIterable):
iterable = m
break
if iterable:
if iterable and iterable.isPairIterator():
def simpleExtendedAttr(str):
return IDLExtendedAttribute(iface.location, (str, ))
nextMethod = IDLMethod(

View File

@ -22,4 +22,5 @@ interface DOMTokenList {
[Throws]
boolean toggle(DOMString token, optional boolean force);
stringifier DOMString ();
iterable<DOMString?>;
};

View File

@ -13,4 +13,5 @@
interface NodeList {
getter Node? item(unsigned long index);
readonly attribute unsigned long length;
iterable<Node?>;
};