Represent BufferSource and ArrayBufferView as ordinary IDL typedefs over their underlying union types, instead of special casing in the IDL generator. This allows the union conversion/return machinery handle these types consistently with other typedefs, which removes buffer specific paths from the IDL generator. This necessitates changing the WebIDL::BufferSource and WebIDL::ArrayBufferView classes as views over these variants. This replaces the old GC backed BufferableObject wrapper structure and provide convenience helpers to determine things such as the byte length, byte offset, backing buffer, and typed-array APIs.
446 lines
16 KiB
C++
446 lines
16 KiB
C++
/*
|
||
* Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
|
||
* Copyright (c) 2023, Luke Wilde <lukew@serenityos.org>
|
||
*
|
||
* SPDX-License-Identifier: BSD-2-Clause
|
||
*/
|
||
|
||
#include <AK/Debug.h>
|
||
#include <LibIDL/Types.h>
|
||
|
||
namespace IDL {
|
||
|
||
Interface& Context::add_interface(NonnullOwnPtr<Interface> interface)
|
||
{
|
||
auto& interface_ref = *interface;
|
||
|
||
if (!interface_ref.name.is_empty())
|
||
interfaces.set(interface_ref.name, &interface_ref);
|
||
|
||
owned_interfaces.append(move(interface));
|
||
return interface_ref;
|
||
}
|
||
|
||
Interface& Context::add_mixin(NonnullOwnPtr<Interface> interface)
|
||
{
|
||
auto& interface_ref = *interface;
|
||
mixins.set(interface_ref.name, &interface_ref);
|
||
|
||
for (auto& partial_mixin : partial_mixins) {
|
||
if (partial_mixin->name == interface_ref.name)
|
||
interface_ref.extend_with_partial_interface(*partial_mixin);
|
||
}
|
||
|
||
owned_mixins.append(move(interface));
|
||
return interface_ref;
|
||
}
|
||
|
||
Module& Context::add_module(NonnullOwnPtr<Module> module)
|
||
{
|
||
auto& module_ref = *module;
|
||
owned_modules.append(move(module));
|
||
return module_ref;
|
||
}
|
||
|
||
Module* Context::find_parsed_module(ByteString const& module_path)
|
||
{
|
||
for (auto const& module : owned_modules) {
|
||
if (module->module_own_path == module_path)
|
||
return module.ptr();
|
||
}
|
||
|
||
return nullptr;
|
||
}
|
||
|
||
ParameterizedType const& Type::as_parameterized() const
|
||
{
|
||
return as<ParameterizedType const>(*this);
|
||
}
|
||
|
||
ParameterizedType& Type::as_parameterized()
|
||
{
|
||
return as<ParameterizedType>(*this);
|
||
}
|
||
|
||
UnionType const& Type::as_union() const
|
||
{
|
||
return as<UnionType const>(*this);
|
||
}
|
||
|
||
UnionType& Type::as_union()
|
||
{
|
||
return as<UnionType>(*this);
|
||
}
|
||
|
||
NonnullRefPtr<Type const> clone_type(Type const& type, bool nullable)
|
||
{
|
||
if (is<ParameterizedType>(type)) {
|
||
Vector<NonnullRefPtr<Type const>> parameters;
|
||
for (auto& parameter : type.as_parameterized().parameters())
|
||
parameters.append(clone_type(parameter, parameter->is_nullable()));
|
||
|
||
return adopt_ref(*new ParameterizedType(type.name(), nullable, move(parameters)));
|
||
}
|
||
|
||
if (is<UnionType>(type)) {
|
||
Vector<NonnullRefPtr<Type const>> member_types;
|
||
for (auto& member_type : type.as_union().member_types())
|
||
member_types.append(clone_type(member_type, member_type->is_nullable()));
|
||
|
||
return adopt_ref(*new UnionType(type.name(), nullable, move(member_types)));
|
||
}
|
||
|
||
return adopt_ref(*new Type(type.name(), nullable));
|
||
}
|
||
|
||
// https://webidl.spec.whatwg.org/#dfn-includes-a-nullable-type
|
||
bool Type::includes_nullable_type() const
|
||
{
|
||
// A type includes a nullable type if:
|
||
// - the type is a nullable type, or
|
||
if (is_nullable())
|
||
return true;
|
||
|
||
// FIXME: - the type is an annotated type and its inner type is a nullable type, or
|
||
|
||
// - the type is a union type and its number of nullable member types is 1.
|
||
if (is_union() && as_union().number_of_nullable_member_types() == 1)
|
||
return true;
|
||
|
||
return false;
|
||
}
|
||
|
||
// https://webidl.spec.whatwg.org/#dfn-includes-undefined
|
||
bool Type::includes_undefined() const
|
||
{
|
||
// A type includes undefined if:
|
||
// - the type is undefined, or
|
||
if (is_undefined())
|
||
return true;
|
||
|
||
// - the type is a nullable type and its inner type includes undefined, or
|
||
// NOTE: We don't treat nullable as its own type, so this is handled by the other cases.
|
||
|
||
// FIXME: - the type is an annotated type and its inner type includes undefined, or
|
||
|
||
// - the type is a union type and one of its member types includes undefined.
|
||
if (is_union())
|
||
return as_union().member_types().contains([](auto& type) { return type->includes_undefined(); });
|
||
|
||
return false;
|
||
}
|
||
|
||
// https://webidl.spec.whatwg.org/#dfn-distinguishable
|
||
bool Type::is_distinguishable_from(IDL::Interface const& interface, IDL::Type const& other) const
|
||
{
|
||
// 1. If one type includes a nullable type and the other type either includes a nullable type,
|
||
// is a union type with flattened member types including a dictionary type, or is a dictionary type,
|
||
// return false.
|
||
if (includes_nullable_type() && (other.includes_nullable_type() || (other.is_union() && any_of(other.as_union().flattened_member_types(), [&interface](auto const& type) { return interface.context.dictionaries.contains(type->name()); })) || interface.context.dictionaries.contains(other.name())))
|
||
return false;
|
||
|
||
// 2. If both types are either a union type or nullable union type, return true if each member type
|
||
// of the one is distinguishable with each member type of the other, or false otherwise.
|
||
if (is_union() && other.is_union()) {
|
||
auto const& this_union = as_union();
|
||
auto const& other_union = other.as_union();
|
||
|
||
for (auto& this_member_type : this_union.member_types()) {
|
||
for (auto& other_member_type : other_union.member_types()) {
|
||
if (!this_member_type->is_distinguishable_from(interface, other_member_type))
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
// 3. If one type is a union type or nullable union type, return true if each member type of the union
|
||
// type is distinguishable with the non-union type, or false otherwise.
|
||
if (is_union() || other.is_union()) {
|
||
auto const& the_union = is_union() ? as_union() : other.as_union();
|
||
auto const& non_union = is_union() ? other : *this;
|
||
|
||
for (auto& member_type : the_union.member_types()) {
|
||
if (!non_union.is_distinguishable_from(interface, member_type))
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
// 4. Consider the two "innermost" types derived by taking each type’s inner type if it is an annotated type,
|
||
// and then taking its inner type inner type if the result is a nullable type. If these two innermost types
|
||
// appear or are in categories appearing in the following table and there is a “●” mark in the corresponding
|
||
// entry or there is a letter in the corresponding entry and the designated additional requirement below the
|
||
// table is satisfied, then return true. Otherwise return false.
|
||
auto const& this_innermost_type = innermost_type();
|
||
auto const& other_innermost_type = other.innermost_type();
|
||
|
||
enum class DistinguishabilityCategory {
|
||
Undefined,
|
||
Boolean,
|
||
Numeric,
|
||
BigInt,
|
||
String,
|
||
Object,
|
||
Symbol,
|
||
InterfaceLike,
|
||
CallbackFunction,
|
||
DictionaryLike,
|
||
SequenceLike,
|
||
__Count
|
||
};
|
||
|
||
// See https://webidl.spec.whatwg.org/#distinguishable-table
|
||
// clang-format off
|
||
static constexpr bool table[to_underlying(DistinguishabilityCategory::__Count)][to_underlying(DistinguishabilityCategory::__Count)] {
|
||
{false, true, true, true, true, true, true, true, true, false, true},
|
||
{ true, false, true, true, true, true, true, true, true, true, true},
|
||
{ true, true, false, true, true, true, true, true, true, true, true},
|
||
{ true, true, true, false, true, true, true, true, true, true, true},
|
||
{ true, true, true, true, false, true, true, true, true, true, true},
|
||
{ true, true, true, true, true, false, true, false, false, false, false},
|
||
{ true, true, true, true, true, true, false, true, true, true, true},
|
||
{ true, true, true, true, true, false, true, false, true, true, true},
|
||
{ true, true, true, true, true, false, true, true, false, false, true},
|
||
{false, true, true, true, true, false, true, true, false, false, true},
|
||
{ true, true, true, true, true, false, true, true, true, true, false},
|
||
};
|
||
// clang-format on
|
||
|
||
auto determine_category = [&interface](Type const& type) -> DistinguishabilityCategory {
|
||
if (type.is_undefined())
|
||
return DistinguishabilityCategory::Undefined;
|
||
if (type.is_boolean())
|
||
return DistinguishabilityCategory::Boolean;
|
||
if (type.is_numeric())
|
||
return DistinguishabilityCategory::Numeric;
|
||
if (type.is_bigint())
|
||
return DistinguishabilityCategory::BigInt;
|
||
if (type.is_string())
|
||
return DistinguishabilityCategory::String;
|
||
if (type.is_object())
|
||
return DistinguishabilityCategory::Object;
|
||
if (type.is_symbol())
|
||
return DistinguishabilityCategory::Symbol;
|
||
// FIXME: InterfaceLike - see below
|
||
// FIXME: CallbackFunction
|
||
// DictionaryLike
|
||
// * Dictionary Types
|
||
// * Record Types
|
||
// FIXME: * Callback Interface Types
|
||
if (interface.context.dictionaries.contains(type.name()) || (type.is_parameterized() && type.name() == "record"sv))
|
||
return DistinguishabilityCategory::DictionaryLike;
|
||
// FIXME: Frozen array types are included in "sequence-like"
|
||
if (type.is_sequence())
|
||
return DistinguishabilityCategory::SequenceLike;
|
||
|
||
// FIXME: For lack of a better way of determining if something is an interface type, this just assumes anything we don't recognise is one.
|
||
dbgln_if(IDL_DEBUG, "Unable to determine category for type named '{}', assuming it's an interface type.", type.name());
|
||
return DistinguishabilityCategory::InterfaceLike;
|
||
};
|
||
|
||
auto this_distinguishability = determine_category(this_innermost_type);
|
||
auto other_distinguishability = determine_category(other_innermost_type);
|
||
|
||
if (this_distinguishability == DistinguishabilityCategory::InterfaceLike && other_distinguishability == DistinguishabilityCategory::InterfaceLike) {
|
||
// The two identified interface-like types are not the same, and
|
||
// FIXME: no single platform object implements both interface-like types.
|
||
return this_innermost_type.name() != other_innermost_type.name();
|
||
}
|
||
|
||
return table[to_underlying(this_distinguishability)][to_underlying(other_distinguishability)];
|
||
}
|
||
|
||
// https://webidl.spec.whatwg.org/#buffer-types
|
||
bool Type::is_buffer() const
|
||
{
|
||
// The buffer types are ArrayBuffer and SharedArrayBuffer.
|
||
return m_name.is_one_of("ArrayBuffer", "SharedArrayBuffer");
|
||
}
|
||
|
||
// https://webidl.spec.whatwg.org/#dfn-typed-array-type
|
||
bool Type::is_typed_array() const
|
||
{
|
||
// The typed array types are Int8Array, Int16Array, Int32Array, Uint8Array, Uint16Array, Uint32Array, Uint8ClampedArray, BigInt64Array, BigUint64Array, Float16Array, Float32Array, and Float64Array.
|
||
return m_name.is_one_of("Int8Array", "Int16Array", "Int32Array", "Uint8Array", "Uint16Array", "Uint32Array", "Uint8ClampedArray", "BigInt64Array", "BigUint64Array", "Float16Array", "Float32Array", "Float64Array");
|
||
}
|
||
|
||
// https://webidl.spec.whatwg.org/#buffer-view-types
|
||
bool Type::is_buffer_view() const
|
||
{
|
||
// The buffer view types are DataView and the typed array types.
|
||
return m_name == "DataView" || is_typed_array();
|
||
}
|
||
|
||
// https://webidl.spec.whatwg.org/#dfn-buffer-source-type
|
||
bool Type::is_buffer_source() const
|
||
{
|
||
// The buffer source types are the buffer types and the buffer view types.
|
||
return is_buffer() || is_buffer_view();
|
||
}
|
||
|
||
// https://webidl.spec.whatwg.org/#dfn-json-types
|
||
bool Type::is_json(Context const& context) const
|
||
{
|
||
// The JSON types are:
|
||
// - numeric types,
|
||
if (is_numeric())
|
||
return true;
|
||
|
||
// - boolean,
|
||
if (is_boolean())
|
||
return true;
|
||
|
||
// - string types,
|
||
if (is_string() || context.enumerations.contains(m_name))
|
||
return true;
|
||
|
||
// - object,
|
||
if (is_object())
|
||
return true;
|
||
|
||
// - nullable types whose inner type is a JSON type,
|
||
// - annotated types whose inner type is a JSON type,
|
||
// NOTE: We don't separate nullable and annotated into separate types.
|
||
|
||
// - union types whose member types are JSON types,
|
||
if (is_union()) {
|
||
auto const& union_type = as_union();
|
||
|
||
for (auto const& type : union_type.member_types()) {
|
||
if (!type->is_json(context))
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
// - typedefs whose type being given a new name is a JSON type,
|
||
auto typedef_iterator = context.typedefs.find(m_name);
|
||
if (typedef_iterator != context.typedefs.end())
|
||
return typedef_iterator->value.type->is_json(context);
|
||
|
||
// - sequence types whose parameterized type is a JSON type,
|
||
// - frozen array types whose parameterized type is a JSON type,
|
||
// - records where all of their values are JSON types,
|
||
if (is_parameterized() && m_name.is_one_of("sequence", "FrozenArray", "record")) {
|
||
auto const& parameterized_type = as_parameterized();
|
||
|
||
for (auto const& parameter : parameterized_type.parameters()) {
|
||
if (!parameter->is_json(context))
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
// - dictionary types where the types of all members declared on the dictionary and all its inherited dictionaries are JSON types,
|
||
auto dictionary_iterator = context.dictionaries.find(m_name);
|
||
if (dictionary_iterator != context.dictionaries.end()) {
|
||
auto const& dictionary = dictionary_iterator->value;
|
||
for (auto const& member : dictionary.members) {
|
||
if (!member.type->is_json(context))
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
// - interface types that have a toJSON operation declared on themselves or one of their inherited interfaces.
|
||
auto current_interface = context.interfaces.get(m_name);
|
||
while (current_interface.has_value()) {
|
||
auto to_json_iterator = current_interface.value()->functions.find_if([](IDL::Function const& function) {
|
||
return function.name == "toJSON"sv;
|
||
});
|
||
|
||
if (to_json_iterator != current_interface.value()->functions.end())
|
||
return true;
|
||
|
||
if (current_interface.value()->parent_name.is_empty())
|
||
break;
|
||
|
||
current_interface = context.interfaces.get(current_interface.value()->parent_name);
|
||
VERIFY(current_interface.has_value());
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
void EffectiveOverloadSet::remove_all_other_entries()
|
||
{
|
||
Vector<Item> new_items;
|
||
new_items.append(m_items[*m_last_matching_item_index]);
|
||
m_items = move(new_items);
|
||
}
|
||
|
||
void Interface::dump()
|
||
{
|
||
dbgln("Attributes:");
|
||
for (auto& attribute : attributes) {
|
||
dbgln(" {}{}{}{} {}",
|
||
attribute.inherit ? "inherit " : "",
|
||
attribute.readonly ? "readonly " : "",
|
||
attribute.type->name(),
|
||
attribute.type->is_nullable() ? "?" : "",
|
||
attribute.name);
|
||
}
|
||
|
||
dbgln("Functions:");
|
||
for (auto& function : functions) {
|
||
dbgln(" {}{} {}",
|
||
function.return_type->name(),
|
||
function.return_type->is_nullable() ? "?" : "",
|
||
function.name);
|
||
for (auto& parameter : function.parameters) {
|
||
dbgln(" {}{} {}",
|
||
parameter.type->name(),
|
||
parameter.type->is_nullable() ? "?" : "",
|
||
parameter.name);
|
||
}
|
||
}
|
||
|
||
dbgln("Static Functions:");
|
||
for (auto& function : static_functions) {
|
||
dbgln(" static {}{} {}",
|
||
function.return_type->name(),
|
||
function.return_type->is_nullable() ? "?" : "",
|
||
function.name);
|
||
for (auto& parameter : function.parameters) {
|
||
dbgln(" {}{} {}",
|
||
parameter.type->name(),
|
||
parameter.type->is_nullable() ? "?" : "",
|
||
parameter.name);
|
||
}
|
||
}
|
||
}
|
||
|
||
void Interface::extend_with_partial_interface(Interface const& partial)
|
||
{
|
||
for (auto const& attribute : partial.attributes) {
|
||
auto attribute_copy = attribute;
|
||
attribute_copy.extended_attributes.update(partial.extended_attributes);
|
||
attributes.append(move(attribute_copy));
|
||
}
|
||
|
||
for (auto const& static_attribute : partial.static_attributes) {
|
||
auto static_attribute_copy = static_attribute;
|
||
static_attribute_copy.extended_attributes.update(partial.extended_attributes);
|
||
static_attributes.append(move(static_attribute_copy));
|
||
}
|
||
|
||
constants.extend(partial.constants);
|
||
|
||
for (auto const& function : partial.functions) {
|
||
auto function_copy = function;
|
||
function_copy.extended_attributes.update(partial.extended_attributes);
|
||
functions.append(move(function_copy));
|
||
}
|
||
|
||
for (auto const& static_function : partial.static_functions) {
|
||
auto static_function_copy = static_function;
|
||
static_function_copy.extended_attributes.update(partial.extended_attributes);
|
||
static_functions.append(move(static_function_copy));
|
||
}
|
||
}
|
||
|
||
}
|