Allow undefined variants in IDL unions (#37042)

Testing: Includes a test which ensures that unions with a `undefined`
variant compile
Fixes https://github.com/servo/servo/issues/28679
Part of https://github.com/servo/servo/issues/30287

---------

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
This commit is contained in:
Simon Wülker 2025-05-23 18:16:22 +02:00 committed by GitHub
parent 9c0c1cf30f
commit fcc391d852
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 37 additions and 10 deletions

View file

@ -39,6 +39,7 @@ from WebIDL import (
from Configuration import (
Configuration,
Descriptor,
DescriptorProvider,
MakeNativeName,
MemberIsLegacyUnforgeable,
getModuleFromObject,
@ -617,16 +618,16 @@ class JSToNativeConversionInfo():
def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None,
isDefinitelyObject=False,
isMember=False,
isDefinitelyObject: bool = False,
isMember: bool | str = False,
isArgument=False,
isAutoRooted=False,
invalidEnumValueFatal=True,
defaultValue=None,
exceptionCode=None,
allowTreatNonObjectAsNull=False,
allowTreatNonObjectAsNull: bool = False,
isCallbackReturnValue=False,
sourceDescription="value"):
sourceDescription="value") -> JSToNativeConversionInfo:
"""
Get a template for converting a JS value to a native object based on the
given type and descriptor. If failureCode is given, then we're actually
@ -5071,7 +5072,7 @@ class CGConstant(CGThing):
return f"pub const {name}: {const_type} = {value};\n"
def getUnionTypeTemplateVars(type, descriptorProvider):
def getUnionTypeTemplateVars(type, descriptorProvider: DescriptorProvider):
if type.isGeckoInterface():
name = type.inner.identifier.name
typeName = descriptorProvider.getDescriptor(name).returnType
@ -5108,6 +5109,12 @@ def getUnionTypeTemplateVars(type, descriptorProvider):
elif type.isCallback():
name = type.name
typeName = f"{name}<D>"
elif type.isUndefined():
return {
"name": type.name,
"typeName": "()",
"jsConversion": CGGeneric("if value.is_undefined() { Ok(Some(())) } else { Ok(None) }")
}
else:
raise TypeError(f"Can't handle {type} in unions yet")
@ -5173,7 +5180,7 @@ impl{self.generic} Clone for {self.type}{self.genericSuffix} {{
def manualImpl(self, t, templateVars):
if t == "Clone":
return self.manualImplClone(templateVars)
raise f"Don't know how to impl {t} for union"
raise ValueError(f"Don't know how to impl {t} for union")
def define(self):
def getTypeWrapper(t):
@ -5330,31 +5337,44 @@ class CGUnionConversionStruct(CGThing):
stringTypes = [t for t in memberTypes if t.isString() or t.isEnum()]
numericTypes = [t for t in memberTypes if t.isNumeric()]
booleanTypes = [t for t in memberTypes if t.isBoolean()]
if stringTypes or numericTypes or booleanTypes:
numUndefinedVariants = [t.isUndefined() for t in memberTypes].count(True)
if stringTypes or numericTypes or booleanTypes or numUndefinedVariants != 0:
assert len(stringTypes) <= 1
assert len(numericTypes) <= 1
assert len(booleanTypes) <= 1
assert numUndefinedVariants <= 1
def getStringOrPrimitiveConversion(memberType):
typename = get_name(memberType)
return CGGeneric(get_match(typename))
other = []
stringConversion = list(map(getStringOrPrimitiveConversion, stringTypes))
numericConversion = list(map(getStringOrPrimitiveConversion, numericTypes))
booleanConversion = list(map(getStringOrPrimitiveConversion, booleanTypes))
undefinedConversion = CGGeneric("return Ok(ConversionResult::Success(Self::Undefined(())));")
if stringConversion:
if booleanConversion:
other.append(CGIfWrapper("value.get().is_boolean()", booleanConversion[0]))
if numericConversion:
other.append(CGIfWrapper("value.get().is_number()", numericConversion[0]))
if numUndefinedVariants != 0:
other.append(CGIfWrapper("value.get().is_undefined()", undefinedConversion))
other.append(stringConversion[0])
elif numericConversion:
if booleanConversion:
other.append(CGIfWrapper("value.get().is_boolean()", booleanConversion[0]))
if numUndefinedVariants != 0:
other.append(CGIfWrapper("value.get().is_undefined()", undefinedConversion))
other.append(numericConversion[0])
else:
assert booleanConversion
elif booleanConversion:
if numUndefinedVariants != 0:
other.append(CGIfWrapper("value.get().is_undefined()", undefinedConversion))
other.append(booleanConversion[0])
else:
assert numUndefinedVariants != 0
other.append(undefinedConversion)
conversions.append(CGList(other, "\n\n"))
conversions.append(CGGeneric(
f'Ok(ConversionResult::Failure("argument could not be converted to any of: {", ".join(names)}".into()))'
@ -5376,6 +5396,10 @@ class CGUnionConversionStruct(CGThing):
post="\n}")
def try_method(self, t):
if t.isUndefined():
# undefined does not require a conversion method, so we don't generate one
return CGGeneric("")
templateVars = getUnionTypeTemplateVars(t, self.descriptorProvider)
actualType = templateVars["typeName"]
if type_needs_tracing(t):
@ -7151,7 +7175,7 @@ impl{self.generic} Clone for {self.makeClassName(self.dictionary)}{self.genericS
def manualImpl(self, t):
if t == "Clone":
return self.manualImplClone()
raise f"Don't know how to impl {t} for dicts."
raise ValueError(f"Don't know how to impl {t} for dicts.")
def struct(self):
d = self.dictionary

View file

@ -627,3 +627,6 @@ dictionary NotUsedAnyWhereElse {};
dictionary RecordFieldWithUnionInside {
record<USVString, (USVString or NotUsedAnyWhereElse)> recordWithUnionField;
};
// https://github.com/servo/servo/issues/28679
typedef (USVString or undefined) UnionWithUndefinedVariant;