ladybird/Meta/Generators/libweb_bindings/to_idl_value.py
Andreas Kling b81269e78b Libraries: Clean up UTF-16 source text paths
Store parser errors, source range filenames, source code filenames,
module source, and Rust parser errors as UTF-16 where they flow back
into JavaScript-visible strings. Keep byte-oriented source buffers
byte-backed.

Remove temporary PrimitiveString, ByteString, and UTF-8 detours from
JSON, RegExp, module debug logging, print formatting, and tests.
2026-06-22 19:51:25 +02:00

1540 lines
68 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Copyright (c) 2026-present, the Ladybird developers.
#
# SPDX-License-Identifier: BSD-2-Clause
from dataclasses import dataclass
from dataclasses import field
from typing import List
from typing import NoReturn
from typing import Optional
from typing import TextIO
from Generators.libweb_bindings.context import GenerationContext
from Generators.libweb_bindings.cpp_types import DictionaryMemberOrAttribute
from Generators.libweb_bindings.cpp_types import InterfaceLikeType
from Generators.libweb_bindings.cpp_types import add_binding_include_for_type
from Generators.libweb_bindings.cpp_types import add_buffer_source_type_include
from Generators.libweb_bindings.cpp_types import add_header_includes_for_type
from Generators.libweb_bindings.cpp_types import add_include_for_string_cpp_type
from Generators.libweb_bindings.cpp_types import converter_function_name
from Generators.libweb_bindings.cpp_types import cpp_empty_value
from Generators.libweb_bindings.cpp_types import cpp_name
from Generators.libweb_bindings.cpp_types import cpp_type
from Generators.libweb_bindings.cpp_types import cpp_type_for_idl_type
from Generators.libweb_bindings.cpp_types import cpp_type_for_idl_type_details
from Generators.libweb_bindings.cpp_types import cpp_type_name_for_string
from Generators.libweb_bindings.cpp_types import cpp_value_type
from Generators.libweb_bindings.cpp_types import fully_qualified_name_for_interface
from Generators.libweb_bindings.cpp_types import implementation_header_for_interface
from Generators.libweb_bindings.cpp_types import interface_like_type_for_idl_type
from Generators.libweb_bindings.cpp_types import is_buffer_source_type
from Generators.libweb_bindings.cpp_types import is_numeric_type
from Generators.libweb_bindings.cpp_types import is_string_type
from Generators.libweb_bindings.cpp_types import is_typed_array_type
from Generators.libweb_bindings.cpp_types import union_type_to_variant
from Generators.libweb_bindings.default_values import cpp_default_value_conversion
from Generators.libweb_bindings.includes import GeneratedIncludes
from Utils.utils import make_name_acceptable_cpp
from Utils.utils import string_to_cpp_enum_name
from Utils.utils import title_case_to_snake_case
from Utils.utils import underlying_type_for_enum
from Utils.webidl_parser import CallbackFunction
from Utils.webidl_parser import Dictionary
from Utils.webidl_parser import Enumeration
from Utils.webidl_parser import IDLParameterizedType
from Utils.webidl_parser import IDLType
from Utils.webidl_parser import IDLUnionType
from Utils.webidl_parser import Interface
@dataclass
class UnionTypes:
boolean_type: Optional[IDLType] = None
numeric_type: Optional[IDLType] = None
string_type: Optional[IDLType] = None
bigint_type: Optional[IDLType] = None
array_buffer_type: Optional[IDLType] = None
data_view_type: Optional[IDLType] = None
sequence_type: Optional[IDLParameterizedType] = None
frozen_array_type: Optional[IDLParameterizedType] = None
record_type: Optional[IDLParameterizedType] = None
dictionary_type: Optional[IDLType] = None
enumeration_types: List[IDLType] = field(default_factory=list)
interface_types: List[IDLType] = field(default_factory=list)
callback_function_types: List[IDLType] = field(default_factory=list)
callback_interface_types: List[IDLType] = field(default_factory=list)
typed_array_types: List[IDLType] = field(default_factory=list)
includes_object_type: bool = False
def collect_union_types(flattened_member_types: List[IDLType], context: GenerationContext) -> UnionTypes:
types = UnionTypes()
for member_type in flattened_member_types:
if types.boolean_type is None and member_type.name == "boolean":
types.boolean_type = member_type
if types.numeric_type is None and is_numeric_type(member_type.name):
types.numeric_type = member_type
if types.string_type is None and is_string_type(member_type.name):
types.string_type = member_type
if types.bigint_type is None and member_type.name == "bigint":
types.bigint_type = member_type
if context.enumeration(member_type) is not None:
types.enumeration_types.append(member_type)
if interface_like_type_for_idl_type(member_type, context) is not None:
types.interface_types.append(member_type)
if context.callback_function(member_type) is not None:
types.callback_function_types.append(member_type)
interface = context.interface(member_type)
if interface is not None and interface.is_callback_interface:
types.callback_interface_types.append(member_type)
if member_type.name == "object":
types.includes_object_type = True
if types.array_buffer_type is None and member_type.name == "ArrayBuffer":
types.array_buffer_type = member_type
if types.data_view_type is None and member_type.name == "DataView":
types.data_view_type = member_type
if is_typed_array_type(member_type):
types.typed_array_types.append(member_type)
if types.dictionary_type is None and context.dictionary(member_type) is not None:
types.dictionary_type = member_type
if not isinstance(member_type, IDLParameterizedType):
continue
if types.sequence_type is None and member_type.name == "sequence":
types.sequence_type = member_type
if types.frozen_array_type is None and member_type.name == "FrozenArray":
types.frozen_array_type = member_type
if types.record_type is None and member_type.name == "record":
types.record_type = member_type
return types
def unsupported_to_idl_value(idl_type: IDLType) -> str:
raise RuntimeError(f"Unsupported IDL value conversion for '{idl_type}'")
def idl_value_conversion_is_throwing(idl_type: IDLType, context: GenerationContext) -> bool:
return idl_type.name not in ("any", "boolean")
def write_enumeration_declaration(out: TextIO, enumeration: Enumeration, includes: GeneratedIncludes) -> None:
includes.add("LibJS/Forward.h")
includes.add("LibJS/Runtime/Value.h")
out.write(f"enum class {enumeration.name} : {underlying_type_for_enum(len(enumeration.values))} {{\n")
for value in enumeration.values:
out.write(f" {string_to_cpp_enum_name(value)},\n")
out.write("};\n\n")
out.write(
f"JS::ThrowCompletionOr<{enumeration.name}> {converter_function_name(enumeration)}(JS::VM&, JS::Value);\n\n"
)
def write_dictionary_declaration(
out: TextIO,
dictionary: Dictionary,
includes: GeneratedIncludes,
context: GenerationContext,
) -> None:
includes.add("LibJS/Forward.h")
includes.add("LibJS/Runtime/Value.h")
parent_dictionary = context.dictionary_parent(dictionary)
if parent_dictionary is not None:
includes.add_binding(parent_dictionary.path.stem)
parent = f" : public {dictionary.parent_name}" if dictionary.parent_name else ""
out.write(f"struct {dictionary.name}{parent} {{\n")
for member in dictionary.members:
add_header_includes_for_type(member, includes, context)
default_value = ""
if member.default_value is not None:
default_value = f" {cpp_default_value_conversion(member, context)} "
member_cpp_type = cpp_type(member, context)
if member.required and member.default_value is None:
out.write(f" {member_cpp_type} {cpp_name(member)};\n")
else:
out.write(f" {member_cpp_type} {cpp_name(member)} {{{default_value}}};\n")
out.write("};\n\n")
out.write(
f"JS::ThrowCompletionOr<{dictionary.name}> {converter_function_name(dictionary)}(JS::VM&, JS::Value);\n\n"
)
def dictionaries_in_dependency_order(dictionaries: List[Dictionary], context: GenerationContext) -> List[Dictionary]:
local_dictionaries = {dictionary.name: dictionary for dictionary in dictionaries}
emitted: set[str] = set()
visiting: set[str] = set()
ordered_dictionaries: List[Dictionary] = []
def dependency_names_for(dictionary: Dictionary) -> set[str]:
dependency_names = {dictionary.parent_name} if dictionary.parent_name else set()
for member in dictionary.members:
dependency_names.update(context.dictionary_type_names(member.type))
dependency_names.discard(dictionary.name)
return dependency_names
def visit(dictionary: Dictionary) -> None:
if dictionary.name in emitted:
return
if dictionary.name in visiting:
raise RuntimeError(f"Dictionary '{dictionary.name}' depends on itself")
visiting.add(dictionary.name)
for dependency_name in dependency_names_for(dictionary):
dependency = local_dictionaries.get(dependency_name)
if dependency is not None:
visit(dependency)
visiting.remove(dictionary.name)
ordered_dictionaries.append(dictionary)
emitted.add(dictionary.name)
for dictionary in local_dictionaries.values():
visit(dictionary)
return ordered_dictionaries
def write_enumeration_conversion(out: TextIO, enumeration: Enumeration, includes: GeneratedIncludes) -> None:
includes.add("LibJS/Runtime/Error.h")
includes.add("LibJS/Runtime/VM.h")
includes.add("LibJS/Runtime/ValueInlines.h")
out.write(
f"""// https://webidl.spec.whatwg.org/#idl-enumeration
JS::ThrowCompletionOr<{enumeration.name}> {converter_function_name(enumeration)}(JS::VM& vm, JS::Value value)
{{
// 1. Let S be the result of calling ? ToString(V).
auto value_as_string = TRY(value.to_utf16_string(vm));
// 2. If S is not one of Es enumeration values, then throw a TypeError.
// 3. Return the enumeration value of type E that is equal to S.
"""
)
for value in enumeration.values:
out.write(f' if (value_as_string == "{value}"sv)\n')
out.write(f" return {enumeration.name}::{string_to_cpp_enum_name(value)};\n")
out.write(
f""" return vm.throw_completion<JS::TypeError>(JS::ErrorType::InvalidEnumerationValue, value_as_string, "{enumeration.name}");
}}
"""
)
def write_dictionary_conversion(
out: TextIO,
dictionary: Dictionary,
includes: GeneratedIncludes,
context: GenerationContext,
) -> None:
includes.add("LibJS/Runtime/Error.h")
includes.add("LibJS/Runtime/VM.h")
includes.add("LibJS/Runtime/Value.h")
includes.add("LibJS/Runtime/ValueInlines.h")
includes.add("LibWeb/Bindings/ExceptionOrUtils.h")
parent_dictionary = context.dictionary_parent(dictionary)
out.write(f"""// https://webidl.spec.whatwg.org/#es-dictionary
JS::ThrowCompletionOr<{dictionary.name}> {converter_function_name(dictionary)}(JS::VM& vm, JS::Value js_dict)
{{
// 1. If jsDict is not an Object and jsDict is neither undefined nor null, then throw a TypeError.
if (!js_dict.is_object() && !js_dict.is_nullish())
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "{dictionary.name}");
// 2. Let idlDict be an empty ordered map, representing a dictionary of type D.
// 3. Let dictionaries be a list consisting of D and all of Ds inherited dictionaries, in order from least to most derived.
// 4. For each dictionary dictionary in dictionaries, in order:
// NB: We defer construction until the return initializer because some members may not be default-constructible. Inherited dictionaries are represented by the generated C++ struct inheritance.
// 5. Return idlDict.
return {dictionary.name} {{
""")
if parent_dictionary is not None:
out.write(f" TRY({converter_function_name(parent_dictionary)}(vm, js_dict)),\n")
# 4.1. For each dictionary member member declared on dictionary, in lexicographical order:
for member in dictionary.members:
conversion = to_idl_value(member, "js_member_value", includes, context)
member_cpp_type = cpp_type(member, context)
member_designator = "" if parent_dictionary is not None else f".{cpp_name(member)} = "
out.write(
f""" {member_designator}TRY([&]() -> JS::ThrowCompletionOr<{member_cpp_type}> {{
// 1. Let key be the identifier of member.
// 2. If jsDict is either undefined or null, then:
// 1. Let jsMemberValue be undefined.
// 3. Otherwise,
// 1. Let jsMemberValue be ? Get(jsDict, key).
auto js_member_value = JS::js_undefined();
if (js_dict.is_object())
js_member_value = TRY(js_dict.as_object().get("{member.name}"_utf16_fly_string));
// 4. If jsMemberValue is not undefined, then:
if (!js_member_value.is_undefined()) {{
// 1. Let idlMemberValue be the result of converting jsMemberValue to an IDL value whose type is the type member is declared to be of.
auto idl_member_value = TRY(throw_dom_exception_if_needed(vm, [&] {{ return {conversion}; }}));
// 2. Set idlDict[key] to idlMemberValue.
return idl_member_value;
}}
"""
)
if member.default_value is not None:
out.write(
f""" // 5. Otherwise, if jsMemberValue is undefined but member has a default value, then:
// 1. Let idlMemberValue be the result of converting member's default value to an IDL value whose type is the type member is declared to be of.
auto idl_member_value = {cpp_default_value_conversion(member, context)};
// 2. Set idlDict[key] to idlMemberValue.
return idl_member_value;
"""
)
elif member.required:
out.write(
f""" // 6. Otherwise, if jsMemberValue is undefined and member is required, then throw a TypeError.
return vm.throw_completion<JS::TypeError>(JS::ErrorType::MissingRequiredProperty, "{member.name}");
"""
)
else:
out.write(
f""" // 7. Otherwise, jsMemberValue is undefined and the member is optional.
return {cpp_empty_value(member, context)};
"""
)
out.write(" }()),\n")
out.write(
""" };
}
"""
)
# 3.2.1. any, https://webidl.spec.whatwg.org/#js-any
def any_to_idl_value(value_name: str, includes: GeneratedIncludes) -> str:
includes.add("LibJS/Runtime/Value.h")
# 1. If V is undefined, then return the unique undefined IDL value.
# 2. If V is null, then return the null object? reference.
# 3. If V is a Boolean, then return the boolean value that represents the same truth value.
# 4. If V is a Number, then return the result of converting V to an unrestricted double.
# 5. If V is a BigInt, then return the result of converting V to a bigint.
# 6. If V is a String, then return the result of converting V to a DOMString.
# 7. If V is a Symbol, then return the result of converting V to a symbol.
# 8. If V is an Object, then return an IDL object value that references V.
# NB: We're getting passed a JS::Value - which is already our C++ representation, so we can just return it as-is.
return value_name
# 3.2.3. boolean, https://webidl.spec.whatwg.org/#js-boolean
def boolean_to_idl_value(value_name: str, includes: GeneratedIncludes) -> str:
includes.add("LibJS/Runtime/Value.h")
# 1. Let x be the result of computing ToBoolean(V).
# 2. Return the IDL boolean value that is the one that represents the same truth value as the JavaScript Boolean value x.
return f"{value_name}.to_boolean()"
# https://webidl.spec.whatwg.org/#abstract-opdef-converttoint
def convert_to_int(
cpp_type: str,
value_name: str,
includes: GeneratedIncludes,
extended_attributes: dict[str, str],
) -> str:
includes.add("LibWeb/WebIDL/Types.h")
includes.add("LibWeb/WebIDL/AbstractOperations.h")
enforce_range = "Yes" if "EnforceRange" in extended_attributes else "No"
clamp = "Yes" if "Clamp" in extended_attributes else "No"
return (
f"WebIDL::convert_to_int<{cpp_type}>(vm, {value_name}, "
f"WebIDL::EnforceRange::{enforce_range}, WebIDL::Clamp::{clamp})"
)
# 3.2.4.1. byte, https://webidl.spec.whatwg.org/#js-byte
def byte_to_idl_value(value_name: str, includes: GeneratedIncludes, extended_attributes: dict[str, str]) -> str:
# 1. Let x be ? ConvertToInt(V, 8, "signed").
# 2. Return the IDL byte value that represents the same numeric value as x.
return convert_to_int("WebIDL::Byte", value_name, includes, extended_attributes)
# 3.2.4.2. octet, https://webidl.spec.whatwg.org/#js-octet
def octet_to_idl_value(value_name: str, includes: GeneratedIncludes, extended_attributes: dict[str, str]) -> str:
# 1. Let x be ? ConvertToInt(V, 8, "unsigned").
# 2. Return the IDL octet value that represents the same numeric value as x.
return convert_to_int("WebIDL::Octet", value_name, includes, extended_attributes)
# 3.2.4.3. short, https://webidl.spec.whatwg.org/#js-short
def short_to_idl_value(value_name: str, includes: GeneratedIncludes, extended_attributes: dict[str, str]) -> str:
# 1. Let x be ? ConvertToInt(V, 16, "signed").
# 2. Return the IDL short value that represents the same numeric value as x.
return convert_to_int("WebIDL::Short", value_name, includes, extended_attributes)
# 3.2.4.4. unsigned short, https://webidl.spec.whatwg.org/#js-unsigned-short
def unsigned_short_to_idl_value(
value_name: str, includes: GeneratedIncludes, extended_attributes: dict[str, str]
) -> str:
# 1. Let x be ? ConvertToInt(V, 16, "unsigned").
# 2. Return the IDL unsigned short value that represents the same numeric value as x.
return convert_to_int("WebIDL::UnsignedShort", value_name, includes, extended_attributes)
# 3.2.4.5. long, https://webidl.spec.whatwg.org/#js-long
def long_to_idl_value(value_name: str, includes: GeneratedIncludes, extended_attributes: dict[str, str]) -> str:
# 1. Let x be ? ConvertToInt(V, 32, "signed").
# 2. Return the IDL long value that represents the same numeric value as x.
return convert_to_int("WebIDL::Long", value_name, includes, extended_attributes)
# 3.2.4.6. unsigned long, https://webidl.spec.whatwg.org/#js-unsigned-long
def unsigned_long_to_idl_value(
value_name: str, includes: GeneratedIncludes, extended_attributes: dict[str, str]
) -> str:
# 1. Let x be ? ConvertToInt(V, 32, "unsigned").
# 2. Return the IDL unsigned long value that represents the same numeric value as x.
return convert_to_int("WebIDL::UnsignedLong", value_name, includes, extended_attributes)
# 3.2.4.7. long long, https://webidl.spec.whatwg.org/#js-long-long
def long_long_to_idl_value(value_name: str, includes: GeneratedIncludes, extended_attributes: dict[str, str]) -> str:
# 1. Let x be ? ConvertToInt(V, 64, "signed").
# 2. Return the IDL long long value that represents the same numeric value as x.
return convert_to_int("WebIDL::LongLong", value_name, includes, extended_attributes)
# 3.2.4.8. unsigned long long, https://webidl.spec.whatwg.org/#js-unsigned-long-long
def unsigned_long_long_to_idl_value(
value_name: str,
includes: GeneratedIncludes,
extended_attributes: dict[str, str],
) -> str:
# 1. Let x be ? ConvertToInt(V, 64, "unsigned").
# 2. Return the IDL unsigned long long value that represents the same numeric value as x.
return convert_to_int("WebIDL::UnsignedLongLong", value_name, includes, extended_attributes)
# 3.2.5. float, https://webidl.spec.whatwg.org/#js-float
def float_to_idl_value(value_name: str, includes: GeneratedIncludes, identifier: str) -> str:
includes.add("LibJS/Runtime/Error.h")
includes.add("LibJS/Runtime/ValueInlines.h")
# 1. Let x be ? ToNumber(V).
# 2. If x is NaN, +∞, or −∞, then throw a TypeError.
# 3. Let S be the set of finite IEEE 754 single-precision floating point values except 0, but with two special values added: 2^128 and 2^128.
# 4. Let y be the number in S that is closest to x, selecting the number with an even significand if there are two equally close values. (The two special values 2^128 and 2^128 are considered to have even significands for this purpose.)
# 5. If y is 2^128 or 2^128, then throw a TypeError.
# 6. If y is +0 and x is negative, return 0.
# 7. Return y.
# FIXME: Correctly implement steps 3-7.
return f"""[&]() -> JS::ThrowCompletionOr<float> {{
float x = TRY({value_name}.to_double(vm));
if (isinf(x) || isnan(x))
return vm.throw_completion<JS::TypeError>(JS::ErrorType::InvalidRestrictedFloatingPointParameter, "{identifier}");
return x;
}}()"""
# 3.2.6. unrestricted float, https://webidl.spec.whatwg.org/#js-unrestricted-float
def unrestricted_float_to_idl_value(value_name: str, includes: GeneratedIncludes) -> str:
includes.add("LibJS/Runtime/ValueInlines.h")
# 1. Let x be ? ToNumber(V).
# 2. If x is NaN, then return the IDL unrestricted float value that represents the IEEE 754 NaN value with the bit pattern 0x7fc00000 [IEEE-754].
# 3. Let S be the set of finite IEEE 754 single-precision floating point values except 0, but with two special values added: 2^128 and 2^128.
# 4. Let y be the number in S that is closest to x, selecting the number with an even significand if there are two equally close values. (The two special values 2^128 and 2^128 are considered to have even significands for this purpose.)
# 5. If y is 2^128, return +∞.
# 6. If y is 2^128, return −∞.
# 7. If y is +0 and x is negative, return 0.
# 8. Return y.
# FIXME: Correctly implement steps 2-8.
return f"""[&]() -> JS::ThrowCompletionOr<float> {{
return static_cast<float>(TRY({value_name}.to_double(vm)));
}}()"""
# 3.2.7. double, https://webidl.spec.whatwg.org/#js-double
def double_to_idl_value(value_name: str, includes: GeneratedIncludes, identifier: str) -> str:
includes.add("LibJS/Runtime/Error.h")
includes.add("LibJS/Runtime/ValueInlines.h")
# 1. Let x be ? ToNumber(V).
# 2. If x is NaN, +∞, or −∞, then throw a TypeError.
# 3. Return the IDL double value that represents the same numeric value as x.
return f"""[&]() -> JS::ThrowCompletionOr<double> {{
auto x = TRY({value_name}.to_double(vm));
if (isinf(x) || isnan(x))
return vm.throw_completion<JS::TypeError>(JS::ErrorType::InvalidRestrictedFloatingPointParameter, "{identifier}");
return x;
}}()"""
# 3.2.8. unrestricted double, https://webidl.spec.whatwg.org/#js-unrestricted-double
def unrestricted_double_to_idl_value(value_name: str, includes: GeneratedIncludes) -> str:
includes.add("LibJS/Runtime/ValueInlines.h")
# 1. Let x be ? ToNumber(V).
# 2. If x is NaN, then return the IDL unrestricted double value that represents the IEEE 754 NaN value with the bit pattern 0x7ff8000000000000 [IEEE-754].
# 3. Return the IDL unrestricted double value that represents the same numeric value as x.
# FIXME!
return f"{value_name}.to_double(vm)"
# 3.2.9. bigint, https://webidl.spec.whatwg.org/#js-bigint
def bigint_to_idl_value(value_name: str, includes: GeneratedIncludes) -> str:
includes.add("LibJS/Runtime/BigInt.h")
includes.add("LibJS/Runtime/Value.h")
includes.add("LibJS/Runtime/ValueInlines.h")
# 1. Let x be ? ToBigInt(V).
# 2. Return the IDL bigint value that represents the same numeric value as x.
return f"{value_name}.to_bigint(vm)"
# 3.2.10. DOMString, https://webidl.spec.whatwg.org/#js-DOMString
def dom_string_to_idl_value(
value_name: str,
includes: GeneratedIncludes,
extended_attributes: Optional[dict[str, str]] = None,
type_name: str = "DOMString",
) -> str:
includes.add("LibJS/Runtime/Value.h")
includes.add("LibWeb/WebIDL/AbstractOperations.h")
cpp_type_name = cpp_type_name_for_string(type_name, extended_attributes)
add_include_for_string_cpp_type(cpp_type_name, includes)
to_string = "to_utf16_string" if "Utf16" in type_name else "to_string"
# 1. If V is null and the conversion is to an IDL type associated with the [LegacyNullToEmptyString] extended attribute, then return the DOMString value that represents the empty string.
if extended_attributes is not None and "LegacyNullToEmptyString" in extended_attributes:
return f"""[&]() -> JS::ThrowCompletionOr<{cpp_type_name}> {{
{cpp_type_name} string;
if (!{value_name}.is_null())
string = TRY(WebIDL::{to_string}(vm, {value_name}));
return string;
}}()"""
# 2. Let x be ? ToString(V).
# 3. Return the IDL DOMString value that represents the same sequence of code units as the one the JavaScript String value x represents.
return f"""[&]() -> JS::ThrowCompletionOr<{cpp_type_name}> {{
return TRY(WebIDL::{to_string}(vm, {value_name}));
}}()"""
# 3.2.11. ByteString, https://webidl.spec.whatwg.org/#js-ByteString
def bytestring_to_idl_value(value_name: str, includes: GeneratedIncludes) -> str:
includes.add("LibJS/Runtime/Value.h")
includes.add("LibWeb/WebIDL/AbstractOperations.h")
cpp_type_name = cpp_type_name_for_string("ByteString")
add_include_for_string_cpp_type(cpp_type_name, includes)
# 1. Let x be ? ToString(V).
# 2. If the value of any element of x is greater than 255, then throw a TypeError.
# 3. Return an IDL ByteString value whose length is the length of x, and where the value of each element is the value of the corresponding element of x.
return f"""[&]() -> JS::ThrowCompletionOr<{cpp_type_name}> {{
return TRY(WebIDL::to_byte_string(vm, {value_name}));
}}()"""
# 3.2.12. USVString, https://webidl.spec.whatwg.org/#js-USVString
def usv_string_to_idl_value(
value_name: str,
includes: GeneratedIncludes,
extended_attributes: Optional[dict[str, str]] = None,
type_name: str = "USVString",
) -> str:
includes.add("LibJS/Runtime/Value.h")
includes.add("LibWeb/WebIDL/AbstractOperations.h")
cpp_type_name = cpp_type_name_for_string(type_name, extended_attributes)
add_include_for_string_cpp_type(cpp_type_name, includes)
to_string = "to_utf16_usv_string" if "Utf16" in type_name else "to_usv_string"
# 1. Let string be the result of converting V to a DOMString.
# 2. If x contains any lone surrogates, then throw a TypeError.
# 3. Return the IDL USVString value that represents the same sequence of code units as the one the JavaScript String value x represents.
return f"""[&]() -> JS::ThrowCompletionOr<{cpp_type_name}> {{
return TRY(WebIDL::{to_string}(vm, {value_name}));
}}()"""
# 3.2.13. object, https://webidl.spec.whatwg.org/#js-object
def object_to_idl_value(value_name: str, includes: GeneratedIncludes) -> str:
includes.add("LibJS/Runtime/Error.h")
includes.add("LibJS/Runtime/Object.h")
includes.add("LibJS/Runtime/Value.h")
includes.add("LibJS/Runtime/ValueInlines.h")
# 1. If V is not an Object, then throw a TypeError.
# 2. Return the IDL object value that is a reference to the same object as V.
return f"""[&]() -> JS::ThrowCompletionOr<GC::Ref<JS::Object>> {{
if (!{value_name}.is_object())
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObject, {value_name});
return GC::Ref {{ {value_name}.as_object() }};
}}()"""
# 3.2.14. symbol, https://webidl.spec.whatwg.org/#js-symbol
def symbol_to_idl_value(value_name: str, includes: GeneratedIncludes) -> str:
includes.add("LibJS/Runtime/Value.h")
# 1. If V is not a Symbol, then throw a TypeError.
# 2. Return the result of converting V to an IDL symbol value.
raise RuntimeError("symbol to IDL value conversion is not yet implemented")
# 3.2.15. Interface types, https://webidl.spec.whatwg.org/#js-interface
def interface_to_idl_value(
value_name: str,
includes: GeneratedIncludes,
interface_like_type: InterfaceLikeType,
) -> str:
includes.add("LibJS/Runtime/Error.h")
includes.add("LibJS/Runtime/Value.h")
includes.add("LibJS/Runtime/ValueInlines.h")
includes.add(interface_like_type.implementation_header)
# 1. If V implements I, then return the IDL interface type value that represents a reference to that platform object.
# 2. Throw a TypeError.
return f"""[&]() -> JS::ThrowCompletionOr<GC::Ref<{interface_like_type.fully_qualified_name}>> {{
if (auto impl = {value_name}.as_if<{interface_like_type.fully_qualified_name}>())
return *impl;
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "{interface_like_type.name}");
}}()"""
# 3.2.16. Callback interface types, https://webidl.spec.whatwg.org/#js-callback-interface
def callback_interface_to_idl_value(
value_name: str,
includes: GeneratedIncludes,
interface: Interface,
) -> str:
includes.add("LibGC/Heap.h")
includes.add("LibJS/Runtime/Error.h")
includes.add("LibJS/Runtime/Value.h")
includes.add("LibJS/Runtime/ValueInlines.h")
includes.add("LibWeb/Bindings/ExceptionOrUtils.h")
includes.add("LibWeb/HTML/Scripting/Environments.h")
includes.add("LibWeb/WebIDL/CallbackType.h")
includes.add(implementation_header_for_interface(interface))
cpp_type = fully_qualified_name_for_interface(interface)
# 1. If V is not an Object, then throw a TypeError.
# 2. Return the IDL callback interface type value that represents a reference to V, with the incumbent settings object as the callback context.
return f"""[&]() -> JS::ThrowCompletionOr<GC::Ref<{cpp_type}>> {{
if (!{value_name}.is_object())
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObject, {value_name});
auto callback_type = vm.heap().allocate<WebIDL::CallbackType>({value_name}.as_object(), HTML::incumbent_realm());
return TRY(throw_dom_exception_if_needed(vm, [&] {{ return {cpp_type}::create(realm, callback_type); }}));
}}()"""
# 3.2.17. Dictionary types, https://webidl.spec.whatwg.org/#js-dictionary
def dictionary_to_idl_value(
idl_type: IDLType,
value_name: str,
includes: GeneratedIncludes,
context: GenerationContext,
) -> str:
add_binding_include_for_type(idl_type, includes, context)
# The actual implementation of this conversion function is generated by write_dictionary_conversion().
return f"{converter_function_name(idl_type)}(vm, {value_name})"
# 3.2.18. Enumeration types, https://webidl.spec.whatwg.org/#js-enumeration
def enumeration_to_idl_value(
idl_type: IDLType,
value_name: str,
includes: GeneratedIncludes,
context: GenerationContext,
) -> str:
add_binding_include_for_type(idl_type, includes, context)
# The actual implementation of this conversion function is generated by write_enumeration_conversion().
return f"{converter_function_name(idl_type)}(vm, {value_name})"
# 3.2.19. Callback function types, https://webidl.spec.whatwg.org/#js-callback-function
def callback_function_to_idl_value(
return_cpp_type: str,
callback_function: CallbackFunction,
value_name: str,
includes: GeneratedIncludes,
) -> str:
includes.add("LibGC/Heap.h")
includes.add("LibJS/Runtime/Error.h")
includes.add("LibJS/Runtime/FunctionObject.h")
includes.add("LibWeb/HTML/Scripting/Environments.h")
includes.add("LibWeb/WebIDL/CallbackType.h")
operation_returns_promise = (
"WebIDL::OperationReturnsPromise::Yes"
if callback_function.return_type.name.split("<", 1)[0] == "Promise"
else "WebIDL::OperationReturnsPromise::No"
)
legacy_treat_non_object_as_null = "LegacyTreatNonObjectAsNull" in callback_function.extended_attributes
return_cpp_type = "GC::Ptr<WebIDL::CallbackType>" if legacy_treat_non_object_as_null else return_cpp_type
conversion = f"""[&]() -> JS::ThrowCompletionOr<{return_cpp_type}> {{
"""
# 1. If the result of calling IsCallable(V) is false and the conversion to an IDL value is not being
# performed due to V being assigned to an attribute whose type is a nullable callback function that
# is annotated with [LegacyTreatNonObjectAsNull], then throw a TypeError.
if not legacy_treat_non_object_as_null:
conversion += f"""
if (!{value_name}.is_function())
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAFunction, {value_name});
"""
# 2. Return the IDL callback function type value that represents a reference to the same object that V
# represents, with the incumbent settings object as the callback context.
conversion += f"""
return vm.heap().allocate<WebIDL::CallbackType>({value_name}.as_object(), HTML::incumbent_realm(), {operation_returns_promise});
}}()"""
return conversion
# 3.2.20. Nullable types — T?, https://webidl.spec.whatwg.org/#js-nullable-type
def nullable_to_idl_value(
idl_type: IDLType,
identifier: str,
extended_attributes: dict[str, str],
return_cpp_type: str,
value_name: str,
includes: GeneratedIncludes,
context: GenerationContext,
) -> str:
inner_type = idl_type.clone_with_nullable(False)
cpp_type = cpp_type_for_idl_type_details(
idl_type,
context,
extended_attributes=extended_attributes,
)
conversion = f"""[&]() -> JS::ThrowCompletionOr<{return_cpp_type}> {{
{cpp_type.name} value;
"""
# 1. If V is not an Object, and the conversion to an IDL value is being performed due to V being assigned to an
# attribute whose type is a nullable callback function that is annotated with [LegacyTreatNonObjectAsNull],
# then return the IDL nullable type T? value null.
callback_function = context.callback_function(inner_type)
legacy_treat_non_object_as_null = (
callback_function is not None and "LegacyTreatNonObjectAsNull" in callback_function.extended_attributes
)
if legacy_treat_non_object_as_null:
conversion += f" if ({value_name}.is_object()) {{\n"
inner_conversion = to_idl_value_from_type(
inner_type, identifier, extended_attributes, value_name, includes, context
)
inner_return = (
f"TRY({inner_conversion})" if idl_value_conversion_is_throwing(inner_type, context) else inner_conversion
)
inner_type_includes_undefined = inner_type.includes_undefined()
# 2. Otherwise, if V is undefined, and T includes undefined, return the unique undefined value.
if inner_type_includes_undefined:
conversion += f"""
if ({value_name}.is_undefined()) {{
value = Empty {{}};
}}
"""
# 3. Otherwise, if V is null or undefined, then return the IDL nullable type T? value null.
conversion += f"""
else if (!{value_name}.is_nullish()) {{
"""
else:
# 3. Otherwise, if V is null or undefined, then return the IDL nullable type T? value null.
conversion += f"""
if (!{value_name}.is_nullish()) {{
"""
# 4. Otherwise, return the result of converting V using the rules for the inner IDL type T.
conversion += f"""
value = {inner_return};
}}
"""
if legacy_treat_non_object_as_null:
conversion += " }\n"
conversion += """ return value;
}()"""
return conversion
# 3.2.21. Sequences — sequence<T>, https://webidl.spec.whatwg.org/#js-sequence
def sequence_to_idl_value(
sequence_type: IDLParameterizedType,
identifier: str,
value_name: str,
includes: GeneratedIncludes,
context: GenerationContext,
) -> str:
element_type = sequence_type.parameters[0]
element_cpp_type = cpp_type_for_idl_type_details(element_type, context)
storage_type_name = element_cpp_type.contained_storage_type.value
# 1. If V is not an Object, throw a TypeError.
# 2. Let method be ? GetMethod(V, %Symbol.iterator%).
# 3. If method is undefined, throw a TypeError.
# 4. Return the result of creating a sequence from V and method.
return f"""[&]() -> JS::ThrowCompletionOr<{storage_type_name}<{element_cpp_type.name}>> {{
if (!{value_name}.is_object())
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObject, {value_name});
auto method = TRY({value_name}.get_method(vm, vm.well_known_symbol_iterator()));
if (!method)
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotIterable, {value_name});
return TRY({create_sequence_from_iterable(sequence_type, identifier, value_name, "method", includes, context)});
}}()"""
# 3.2.21.1. Creating a sequence from an iterable, https://webidl.spec.whatwg.org/#create-sequence-from-iterable
def create_sequence_from_iterable(
sequence_type: IDLParameterizedType,
identifier: str,
value_name: str,
iterator_method_name: str,
includes: GeneratedIncludes,
context: GenerationContext,
) -> str:
includes.add("LibJS/Runtime/Error.h")
includes.add("LibJS/Runtime/Iterator.h")
includes.add("LibJS/Runtime/Value.h")
includes.add("LibJS/Runtime/ValueInlines.h")
includes.add("LibWeb/Bindings/ExceptionOrUtils.h")
element_type = sequence_type.parameters[0]
element_cpp_type = cpp_type_for_idl_type_details(element_type, context)
storage_type_name = element_cpp_type.contained_storage_type.value
return f"""[&]() -> JS::ThrowCompletionOr<{storage_type_name}<{element_cpp_type.name}>> {{
// To create an IDL value of type sequence<T> given an iterable iterable and an iterator getter method, perform the following steps:
// 1. Let iteratorRecord be ? GetIteratorFromMethod(iterable, method).
auto iterator = TRY(JS::get_iterator_from_method(vm, {value_name}, *{iterator_method_name}));
{storage_type_name}<{element_cpp_type.name}> sequence;
// 2. Initialize i to be 0.
// 3. Repeat
for (;;) {{
// 1. Let next be ? IteratorStepValue(iteratorRecord).
auto next = TRY(JS::iterator_step_value(vm, iterator));
// 2. If next is done, then return an IDL sequence value of type sequence<T> of length i, where the value of the element at index j is Sj.
if (!next.has_value())
break;
// 3. Initialize Si to the result of converting next to an IDL value of type T.
auto next_value = next.release_value();
auto sequence_item = TRY(throw_dom_exception_if_needed(vm, [&] {{ return {to_idl_value_from_type(element_type, identifier, {}, "next_value", includes, context)}; }}));
// 4. Set i to i + 1.
sequence.append(sequence_item);
}}
return sequence;
}}()"""
# 3.2.22. Async sequences — async_sequence<T>, https://webidl.spec.whatwg.org/#js-async-iterable
def async_sequence_to_idl_value() -> NoReturn:
raise RuntimeError("async sequence to IDL value conversion is not yet implemented")
# 3.2.23. Records — record<K, V>, https://webidl.spec.whatwg.org/#js-record
def record_to_idl_value(
record_type: IDLParameterizedType,
identifier: str,
value_name: str,
includes: GeneratedIncludes,
context: GenerationContext,
) -> str:
if len(record_type.parameters) != 2:
raise RuntimeError("Record type must have exactly two parameters")
key_type = record_type.parameters[0]
value_type = record_type.parameters[1]
if not is_string_type(key_type.name):
raise RuntimeError("Record key type must be a string type")
includes.add("LibJS/Runtime/Error.h")
includes.add("LibJS/Runtime/PropertyKey.h")
includes.add("LibJS/Runtime/Value.h")
includes.add("LibJS/Runtime/ValueInlines.h")
record_cpp_type = cpp_type_for_idl_type(record_type, context)
return f"""[&]() -> JS::ThrowCompletionOr<{record_cpp_type}> {{
// An ECMAScript value O is converted to an IDL record<K, V> value as follows:
// 1. If Type(O) is not Object, throw a TypeError.
if (!{value_name}.is_object())
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObject, {value_name});
auto& record_object0 = {value_name}.as_object();
// 2. Let result be a new empty instance of record<K, V>.
{record_cpp_type} record;
// 3. Let keys be ? O.[[OwnPropertyKeys]]().
auto keys = TRY(record_object0.internal_own_property_keys());
// 4. For each key of keys:
for (auto& key : keys) {{
auto property_key = MUST(JS::PropertyKey::from_value(vm, key));
// 1. Let desc be ? O.[[GetOwnProperty]](key).
auto desc = TRY(record_object0.internal_get_own_property(property_key));
// 2. If desc is not undefined and desc.[[Enumerable]] is true:
if (!desc.has_value() || !desc->enumerable.has_value() || !desc->enumerable.value())
continue;
// 1. Let typedKey be key converted to an IDL value of type K.
auto typed_key = TRY({to_idl_value_from_type(key_type, identifier, {}, "key", includes, context)});
// 2. Let value be ? Get(O, key).
auto value = TRY(record_object0.get(property_key));
// 3. Let typedValue be value converted to an IDL value of type V.
auto typed_value = TRY({to_idl_value_from_type(value_type, identifier, {}, "value", includes, context)});
// 4. Set result[typedKey] to typedValue.
record.set(typed_key, typed_value);
}}
// 5. Return result.
return record;
}}()"""
# 3.2.24. Promise types — Promise<T>, https://webidl.spec.whatwg.org/#js-promise
def promise_to_idl_value(
value_name: str,
includes: GeneratedIncludes,
) -> str:
includes.add("LibJS/Runtime/AbstractOperations.h")
includes.add("LibJS/Runtime/PromiseConstructor.h")
# An ECMAScript value V is converted to an IDL Promise<T> value as follows:
return f"""[&]() -> JS::ThrowCompletionOr<GC::Ref<WebIDL::Promise>> {{
auto& realm = *vm.current_realm();
// 1. Let promiseCapability be ? NewPromiseCapability(%Promise%).
auto promise_capability0 = TRY(JS::new_promise_capability(vm, realm.intrinsics().promise_constructor()));
// 2. Perform ? Call(promiseCapability.[[Resolve]], undefined, « V »).
TRY(JS::call(vm, *promise_capability0->resolve(), JS::js_undefined(), {value_name}));
// 3. Return promiseCapability.
return promise_capability0;
}}()"""
# 3.2.25. Union types, https://webidl.spec.whatwg.org/#js-union
def union_to_idl_value(
union_type: IDLUnionType,
identifier: str,
extended_attributes: dict[str, str],
value_name: str,
includes: GeneratedIncludes,
context: GenerationContext,
) -> str:
# https://webidl.spec.whatwg.org/#es-union
includes.add("AK/Variant.h")
variant_type = union_type_to_variant(union_type, context)
types = collect_union_types(union_type.flattened_member_types(), context)
sections: list[str] = []
def conversion_for(member_type: IDLType, name: str = value_name) -> str:
# Null is handled at the union level and stored as Empty in the generated Variant.
return to_idl_value_from_type(
member_type.clone_with_nullable(False),
identifier,
extended_attributes,
name,
includes,
context,
)
def append(section: str) -> None:
if section:
sections.append(section)
# 1. If the union type includes undefined and V is undefined, then return the unique undefined value.
if union_type.includes_undefined():
includes.add("AK/Types.h")
append(f"""
if ({value_name}.is_undefined()) {{
return Empty {{}};
}}
""")
# 2. If the union type includes a nullable type and V is null or undefined, then return the IDL value null.
if union_type.includes_nullable_type():
includes.add("AK/Types.h")
append(f"""
if ({value_name}.is_nullish()) {{
return Empty {{}};
}}
""")
# 4. If V is null or undefined, then:
# 1. If types includes a dictionary type, then return the result of converting V to that dictionary type.
if not union_type.includes_nullable_type() and types.dictionary_type is not None:
append(f"""
if ({value_name}.is_nullish()) {{
return {variant_type} {{ TRY({dictionary_to_idl_value(types.dictionary_type, value_name, includes, context)}) }};
}}
""")
# NB: Doing this here simplifies logic below.
append(f"""
if ({value_name}.is_object()) {{
[[maybe_unused]] auto& object = {value_name}.as_object();
""")
# 5. If V is a platform object, then:
if types.interface_types:
includes.add("LibWeb/Bindings/PlatformObject.h")
append("""
if (is<PlatformObject>(object)) {
""")
for interface_type in types.interface_types:
interface_like_type = interface_like_type_for_idl_type(interface_type, context)
assert interface_like_type is not None
platform_object_cpp_type = interface_like_type.fully_qualified_name
includes.add(interface_like_type.implementation_header)
# 1. If types includes an interface type that V implements, then return the IDL value that is a reference to the object V.
append(f"""
if (auto* result = as_if<{platform_object_cpp_type}>(object))
return {variant_type} {{ GC::Ref {{ *result }} }};
""")
# 2. If types includes object, then return the IDL value that is a reference to the object V.
if types.includes_object_type:
append(f""" return {variant_type} {{ GC::Ref {{ object }} }};
""")
append(""" }
""")
# 6. If Type(V) is Object, V has an [[ArrayBufferData]] internal slot, and IsSharedArrayBuffer(V) is false, then:
if types.array_buffer_type is not None:
includes.add("LibJS/Runtime/ArrayBuffer.h")
array_buffer_conversion = conversion_for(types.array_buffer_type)
# 1. If types includes ArrayBuffer, then return the result of converting V to ArrayBuffer.
# 2. If types includes object, then return the IDL value that is a reference to the object V.
append(f"""
if (auto* array_buffer = as_if<JS::ArrayBuffer>(object)) {{
if (!array_buffer->is_shared_array_buffer()) {{
// 1. If types includes ArrayBuffer, then return the result of converting V to ArrayBuffer.
auto array_buffer_union_type = TRY({array_buffer_conversion});
return {variant_type} {{ array_buffer_union_type }};
}}
}}
""")
# FIXME: 7. If V is an Object, V, has an [[ArrayBufferData]] internal slot, and IsSharedArrayBuffer(V) is true, then:
# 8. If V is an Object and V has a [[DataView]] internal slot, then:
if types.data_view_type is not None:
includes.add("LibJS/Runtime/DataView.h")
data_view_conversion = conversion_for(types.data_view_type)
# 1. If types includes DataView, then return the result of converting V to DataView.
# 2. If types includes object, then return the IDL value that is a reference to the object V.
append(f"""
if (as_if<JS::DataView>(object)) {{
// 1. If types includes DataView, then return the result of converting V to DataView.
auto data_view_union_type = TRY({data_view_conversion});
return {variant_type} {{ data_view_union_type }};
}}
""")
# 9. If V is an Object and V has a [[TypedArrayName]] internal slot, then:
if types.typed_array_types:
includes.add("LibJS/Runtime/TypedArray.h")
for typed_array_type in types.typed_array_types:
typed_array_conversion = conversion_for(typed_array_type)
typed_array_cpp_name = make_name_acceptable_cpp(title_case_to_snake_case(typed_array_type.name))
# 1. If types includes a typed array type whose name is the value of Vs [[TypedArrayName]] internal slot, then return the result of converting V to that type.
# 2. If types includes object, then return the IDL value that is a reference to the object V.
append(f"""
if (as_if<JS::{typed_array_type.name}>(object)) {{
auto {typed_array_cpp_name}_union_type = TRY({typed_array_conversion});
return {variant_type} {{ {typed_array_cpp_name}_union_type }};
}}
""")
# 1. If IsCallable(V) is true, then:
if types.callback_function_types:
callback_function_type = types.callback_function_types[0]
callback_function = context.callback_function(callback_function_type)
assert callback_function is not None
# 1. If types includes a callback function type, then return the result of converting V to that callback function type.
# 2. If types includes object, then return the IDL value that is a reference to the object V.
callback_function_conversion = callback_function_to_idl_value(
cpp_type_for_idl_type(callback_function_type, context, extended_attributes=extended_attributes),
callback_function,
value_name,
includes,
)
append(f"""
if (object.is_function()) {{
auto callback_function_union_type = TRY({callback_function_conversion});
return {variant_type} {{ callback_function_union_type }};
}}
""")
# 11. If V is an Object, then:
# FIXME: 11.1 If types includes an async sequence type, then
# 11.2. If types includes a sequence type, then
if types.sequence_type is not None:
append(f"""
// 1. Let method be ? GetMethod(V, @@iterator).
auto method = TRY({value_name}.get_method(vm, vm.well_known_symbol_iterator()));
// 2. If method is not undefined, return the result of creating a sequence of that type from V and method.
if (method) {{
auto sequence_union_type = TRY({create_sequence_from_iterable(types.sequence_type, identifier, value_name, "method", includes, context)});
return {variant_type} {{ sequence_union_type }};
}}
""")
# 11.3. If types includes a frozen array type, then
if types.frozen_array_type is not None:
append(f"""
// 1. Let method be ? GetMethod(V, @@iterator).
auto frozen_array_method = TRY({value_name}.get_method(vm, vm.well_known_symbol_iterator()));
// 2. If method is not undefined, return the result of creating a frozen array of that type from V and method.
if (frozen_array_method) {{
auto frozen_array_union_type = TRY({create_sequence_from_iterable(types.frozen_array_type, identifier, value_name, "frozen_array_method", includes, context)});
return {variant_type} {{ frozen_array_union_type }};
}}
""")
# 11.4. If types includes a dictionary type, then return the result of converting V to that dictionary type.
if types.dictionary_type is not None:
dictionary_conversion = dictionary_to_idl_value(types.dictionary_type, value_name, includes, context)
append(f" return {variant_type} {{ TRY({dictionary_conversion}) }};\n")
# 11.5. If types includes a record type, then return the result of converting V to that record type.
if types.record_type is not None:
record_conversion = record_to_idl_value(types.record_type, identifier, value_name, includes, context)
append(f"""
auto record_union_type = TRY({record_conversion});
return {variant_type} {{ record_union_type }};
""")
# 11.6. If types includes a callback interface type, then return the result of converting V to that callback interface type.
if types.callback_interface_types:
callback_interface_type = types.callback_interface_types[0]
callback_interface = context.interface(callback_interface_type)
assert callback_interface is not None
callback_interface_conversion = callback_interface_to_idl_value(
value_name,
includes,
callback_interface,
)
append(f"""
auto callback_interface_union_type = TRY({callback_interface_conversion});
return {variant_type} {{ callback_interface_union_type }};
""")
# 11.7. If types includes object, then return the IDL value that is a reference to the object V.
if types.includes_object_type:
append(f"""
return {variant_type} {{ GC::Ref {{ object }} }};
""")
append(" }\n")
# 12. If Type(V) is Boolean, then:
if types.boolean_type is not None:
# 1. If types includes boolean, then return the result of converting V to boolean.
append(f"""
if ({value_name}.is_boolean())
return {variant_type} {{ {value_name}.as_bool() }};
""")
# 13. If V is a Number, then:
if types.numeric_type is not None:
# 1. If types includes a numeric type, then return the result of converting V to that numeric type.
numeric_value_name = f"{make_name_acceptable_cpp(identifier)}_number"
numeric_conversion_expression = conversion_for(types.numeric_type)
append(f"""
if ({value_name}.is_number()) {{
auto {numeric_value_name} = TRY({numeric_conversion_expression});
return {variant_type} {{ {numeric_value_name} }};
}}
""")
# 14. If V is a BigInt, then:
if types.bigint_type is not None:
includes.add("LibJS/Runtime/BigInt.h")
# 1. If types includes bigint, then return the result of converting V to bigint
append(f"""
if ({value_name}.is_bigint())
return {variant_type} {{ GC::Ref {{ {value_name}.as_bigint() }} }};
""")
# 15. If types includes a string type, then return the result of converting V to that type.
# NB: We need to special case enumurations since we don't pass those through as strings.
# FIXME: Can we use a helper here?
if types.enumeration_types:
includes.add("AK/StringView.h")
includes.add("LibJS/Runtime/ValueInlines.h")
enumeration_conversion = f"""
if ({value_name}.is_string()) {{
auto enumeration_string = TRY({value_name}.to_utf16_string(vm));
"""
for enumeration_type in types.enumeration_types:
enumeration = context.enumeration(enumeration_type)
assert enumeration is not None
for enumeration_value in enumeration.values:
enumeration_conversion += f""" if (enumeration_string == "{enumeration_value}"sv)
return {variant_type} {{ {enumeration.name}::{string_to_cpp_enum_name(enumeration_value)} }};
"""
enumeration_conversion += """ }
"""
append(enumeration_conversion)
# 15. If types includes a string type, then return the result of converting V to that type.
if types.string_type is not None:
string_value_name = f"{make_name_acceptable_cpp(identifier)}_string"
string_conversion_expression = conversion_for(types.string_type)
append(f"""
auto {string_value_name} = TRY({string_conversion_expression});
return {variant_type} {{ {string_value_name} }};
""")
# 16. If types includes a numeric type and bigint, then return the result of converting V to either that numeric type or bigint.
if types.numeric_type is not None and types.bigint_type is not None:
includes.add("LibJS/Runtime/BigInt.h")
includes.add("LibJS/Runtime/ValueInlines.h")
numeric_value_name = f"{make_name_acceptable_cpp(identifier)}_number"
numeric_conversion_expression = conversion_for(types.numeric_type, "x")
# https://webidl.spec.whatwg.org/#js-bigint
append(f"""
// An ECMAScript value V is converted to an IDL numeric type T or bigint value by running the following algorithm:
// 1. Let x be ? ToNumeric(V).
auto x = TRY({value_name}.to_numeric(vm));
// 2. If Type(x) is BigInt, then:
if (x.is_bigint())
// 1. Return the IDL bigint value that represents the same numeric value as x.
return {variant_type} {{ GC::Ref {{ x.as_bigint() }} }};
// 3. Assert: Type(x) is Number.
VERIFY(x.is_number());
// 4. Return the result of converting x to T.
auto {numeric_value_name}_fallback = TRY({numeric_conversion_expression});
return {variant_type} {{ {numeric_value_name}_fallback }};
""")
# 17. If types includes a numeric type, then return the result of converting V to that numeric type.
if types.numeric_type is not None:
numeric_value_name = f"{make_name_acceptable_cpp(identifier)}_number"
numeric_conversion_expression = conversion_for(types.numeric_type)
append(f"""
auto {numeric_value_name}_fallback = TRY({numeric_conversion_expression});
return {variant_type} {{ {numeric_value_name}_fallback }};
""")
# 18. If types includes boolean, then return the result of converting V to boolean.
if types.boolean_type is not None:
append(f"""
return {variant_type} {{ {value_name}.to_boolean() }};
""")
# 19. If types includes bigint, then return the result of converting V to bigint.
if types.bigint_type is not None:
append(
f"""
auto bigint_fallback = TRY({bigint_to_idl_value(value_name, includes)});
return {variant_type} {{ bigint_fallback }};
"""
)
# 20. Throw a TypeError.
includes.add("LibJS/Runtime/Error.h")
append("""
return vm.throw_completion<JS::TypeError>("No union types matched"_utf16);
""")
return f"""[&]() -> JS::ThrowCompletionOr<{variant_type}> {{
{"".join(sections)} }}()"""
def buffer_source_to_idl_value(
idl_type: IDLType,
extended_attributes: dict[str, str],
value_cpp_type_name: str,
value_name: str,
includes: GeneratedIncludes,
context: GenerationContext,
) -> str:
type_name = idl_type.name
includes.add("AK/TypeCasts.h")
includes.add("LibJS/Runtime/Error.h")
includes.add("LibJS/Runtime/Value.h")
add_buffer_source_type_include(idl_type, includes)
def array_buffer_checks(buffer_name: str) -> str:
allow_shared_check = ""
if "AllowShared" not in extended_attributes:
allow_shared_check = f""" // 2. If IsSharedArrayBuffer(V) is true, then throw a TypeError.
if ({buffer_name}->is_shared_array_buffer())
return vm.throw_completion<JS::TypeError>(JS::ErrorType::SharedArrayBuffer);
"""
allow_resizable_check = ""
if "AllowResizable" not in extended_attributes:
allow_resizable_check = f""" // 3. If the conversion is not to an IDL type associated with the [AllowResizable] extended attribute, and
// IsFixedLengthArrayBuffer(V) is false, then throw a TypeError.
if (!{buffer_name}->is_fixed_length())
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "fixed-length {type_name}");
"""
return f"{allow_shared_check}{allow_resizable_check}"
def array_buffer_view_checks(buffer_name: str) -> str:
allow_shared_check = ""
if "AllowShared" not in extended_attributes:
allow_shared_check = f""" // 2. If the conversion is not to an IDL type associated with the [AllowShared] extended attribute, and
// IsSharedArrayBuffer(V.[[ViewedArrayBuffer]]) is true, then throw a TypeError.
if ({buffer_name}.is_shared_array_buffer())
return vm.throw_completion<JS::TypeError>(JS::ErrorType::SharedArrayBuffer);
"""
allow_resizable_check = ""
if "AllowResizable" not in extended_attributes:
allow_resizable_check = f""" // 3. If the conversion is not to an IDL type associated with the [AllowResizable] extended attribute, and
// IsFixedLengthArrayBuffer(V.[[ViewedArrayBuffer]]) is false, then throw a TypeError.
if (!{buffer_name}.is_fixed_length())
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "fixed-length {type_name}");
"""
return f"{allow_shared_check}{allow_resizable_check}"
if type_name == "ArrayBuffer":
return f"""[&]() -> JS::ThrowCompletionOr<{value_cpp_type_name}> {{
// A JavaScript value V is converted to an IDL ArrayBuffer value by running the following algorithm:
// 1. If V is not an Object, or V does not have an [[ArrayBufferData]] internal slot, then throw a TypeError.
auto builtin_buffer = {value_name}.as_if<JS::ArrayBuffer>();
if (!builtin_buffer)
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "{type_name}");
{array_buffer_checks("builtin_buffer")} // 4. Return the IDL ArrayBuffer value that is a reference to the same object as V.
return GC::Ref {{ *builtin_buffer }};
}}()"""
if type_name == "DataView":
includes.add("LibJS/Runtime/DataView.h")
return f"""[&]() -> JS::ThrowCompletionOr<{value_cpp_type_name}> {{
// A JavaScript value V is converted to an IDL DataView value by running the following algorithm:
// 1. If V is not an Object, or V does not have a [[DataView]] internal slot, then throw a TypeError.
auto builtin_buffer = {value_name}.as_if<JS::DataView>();
if (!builtin_buffer)
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "{type_name}");
auto& viewed_array_buffer = *builtin_buffer->viewed_array_buffer();
{array_buffer_view_checks("viewed_array_buffer")} // 4. Return the IDL DataView value that is a reference to the same object as V.
return GC::Ref {{ *builtin_buffer }};
}}()"""
if is_typed_array_type(idl_type):
includes.add("LibJS/Runtime/TypedArray.h")
return f"""[&]() -> JS::ThrowCompletionOr<{value_cpp_type_name}> {{
// A JavaScript value V is converted to an IDL typed array value by running the following algorithm:
// 1. Let T be the IDL type V is being converted to.
// 2. If V is not an Object, or V does not have a [[TypedArrayName]] internal slot with a value equal to T's name,
// then throw a TypeError.
auto builtin_buffer = {value_name}.as_if<JS::{type_name}>();
if (!builtin_buffer)
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "{type_name}");
auto& viewed_array_buffer = *builtin_buffer->viewed_array_buffer();
{array_buffer_view_checks("viewed_array_buffer")} // 5. Return the IDL value of type T that is a reference to the same object as V.
return GC::Ref {{ *builtin_buffer }};
}}()"""
return unsupported_to_idl_value(idl_type)
def to_idl_value_from_type(
idl_type: IDLType,
identifier: str,
extended_attributes: dict[str, str],
value_name: str,
includes: GeneratedIncludes,
context: GenerationContext,
return_cpp_type: Optional[str] = None,
value_cpp_type_name: Optional[str] = None,
) -> str:
type_name = idl_type.name
return_cpp_type = return_cpp_type or cpp_type_for_idl_type(
idl_type, context, extended_attributes=extended_attributes
)
value_cpp_type_name = value_cpp_type_name or return_cpp_type
if idl_type.nullable:
return nullable_to_idl_value(
idl_type, identifier, extended_attributes, return_cpp_type, value_name, includes, context
)
if type_name == "any":
return any_to_idl_value(value_name, includes)
if type_name == "boolean":
return boolean_to_idl_value(value_name, includes)
if type_name == "byte":
return byte_to_idl_value(value_name, includes, extended_attributes)
if type_name == "octet":
return octet_to_idl_value(value_name, includes, extended_attributes)
if type_name == "short":
return short_to_idl_value(value_name, includes, extended_attributes)
if type_name == "unsigned short":
return unsigned_short_to_idl_value(value_name, includes, extended_attributes)
if type_name == "long":
return long_to_idl_value(value_name, includes, extended_attributes)
if type_name == "unsigned long":
return unsigned_long_to_idl_value(value_name, includes, extended_attributes)
if type_name == "long long":
return long_long_to_idl_value(value_name, includes, extended_attributes)
if type_name == "unsigned long long":
return unsigned_long_long_to_idl_value(value_name, includes, extended_attributes)
if type_name == "float":
return float_to_idl_value(value_name, includes, identifier)
if type_name == "unrestricted float":
return unrestricted_float_to_idl_value(value_name, includes)
if type_name == "double":
return double_to_idl_value(value_name, includes, identifier)
if type_name == "unrestricted double":
return unrestricted_double_to_idl_value(value_name, includes)
if type_name == "bigint":
return bigint_to_idl_value(value_name, includes)
if type_name in ("DOMString", "Utf16DOMString"):
return dom_string_to_idl_value(
value_name,
includes,
extended_attributes,
type_name,
)
if type_name == "ByteString":
return bytestring_to_idl_value(value_name, includes)
if type_name in ("USVString", "Utf16USVString"):
return usv_string_to_idl_value(
value_name,
includes,
extended_attributes,
type_name,
)
if type_name == "object":
return object_to_idl_value(value_name, includes)
if type_name == "symbol":
return symbol_to_idl_value(value_name, includes)
interface_like_type = interface_like_type_for_idl_type(idl_type, context)
if interface_like_type is not None:
return interface_to_idl_value(value_name, includes, interface_like_type)
interface = context.interface(idl_type)
if interface is not None and interface.is_callback_interface:
return callback_interface_to_idl_value(value_name, includes, interface)
if context.dictionary(idl_type) is not None:
return dictionary_to_idl_value(idl_type, value_name, includes, context)
if context.enumeration(idl_type) is not None:
return enumeration_to_idl_value(idl_type, value_name, includes, context)
callback_function = context.callback_function(idl_type)
if callback_function is not None:
return callback_function_to_idl_value(return_cpp_type, callback_function, value_name, includes)
if isinstance(idl_type, IDLParameterizedType) and type_name == "sequence":
return sequence_to_idl_value(idl_type, identifier, value_name, includes, context)
if isinstance(idl_type, IDLParameterizedType) and type_name == "async_sequence":
return async_sequence_to_idl_value()
if isinstance(idl_type, IDLParameterizedType) and type_name == "record":
return record_to_idl_value(idl_type, identifier, value_name, includes, context)
if isinstance(idl_type, IDLParameterizedType) and type_name == "Promise":
return promise_to_idl_value(value_name, includes)
if isinstance(idl_type, IDLUnionType):
return union_to_idl_value(idl_type, identifier, extended_attributes, value_name, includes, context)
if is_buffer_source_type(idl_type):
return buffer_source_to_idl_value(
idl_type, extended_attributes, value_cpp_type_name, value_name, includes, context
)
if isinstance(idl_type, IDLParameterizedType) and type_name == "FrozenArray":
return sequence_to_idl_value(idl_type, identifier, value_name, includes, context)
return unsupported_to_idl_value(idl_type)
def to_idl_value(
member: DictionaryMemberOrAttribute,
value_name: str,
includes: GeneratedIncludes,
context: GenerationContext,
) -> str:
return to_idl_value_from_type(
member.type,
member.name,
getattr(member, "extended_attributes", {}),
value_name,
includes,
context,
return_cpp_type=cpp_type(member, context),
value_cpp_type_name=cpp_value_type(member, context),
)
def type_check_idl_value(
idl_type: IDLType,
value_name: str,
includes: GeneratedIncludes,
context: GenerationContext,
interface_name: str,
) -> str:
if is_string_type(idl_type.name):
return f""" if (!{value_name}.is_string())
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "String");
"""
interface_like_type = interface_like_type_for_idl_type(idl_type, context)
if interface_like_type is None:
raise RuntimeError(f"Unsupported IDL value type '{idl_type.name}' on '{interface_name}'")
includes.add("AK/TypeCasts.h")
includes.add(interface_like_type.implementation_header)
return f""" if (!{value_name}.is<{interface_like_type.fully_qualified_name}>())
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "{interface_like_type.fully_qualified_name}");
"""