diff options
author | Luke Wilde <lukew@serenityos.org> | 2022-01-25 19:24:00 +0000 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2022-01-31 15:25:36 +0100 |
commit | 68813fbe701c409d00a4f4bc0c6c941ece94d384 (patch) | |
tree | 8e3b7be4f57f277f2a0ba2aa581da37a1a5f629d /Meta/Lagom | |
parent | fb7ca09f048174388b30bf3d87b015b0c1f7ef7b (diff) | |
download | serenity-68813fbe701c409d00a4f4bc0c6c941ece94d384.zip |
LibWeb: Add initial support for passing union types into IDL functions
This currently does not accept ArrayBuffer, DataView, TypedArrays,
callback functions, nullable types, dictionaries, records and frozen
arrays.
Diffstat (limited to 'Meta/Lagom')
-rw-r--r-- | Meta/Lagom/Tools/CodeGenerators/LibWeb/WrapperGenerator.cpp | 517 |
1 files changed, 515 insertions, 2 deletions
diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/WrapperGenerator.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/WrapperGenerator.cpp index b7a4357c80..c9a285d784 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/WrapperGenerator.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/WrapperGenerator.cpp @@ -106,6 +106,135 @@ struct Type : public RefCounted<Type> { String name; bool nullable { false }; bool is_string() const { return name.is_one_of("ByteString", "CSSOMString", "DOMString", "USVString"); } + + // https://webidl.spec.whatwg.org/#dfn-integer-type + bool is_integer() const { return name.is_one_of("byte", "octet", "short", "unsigned short", "long", "unsigned long", "long long", "unsigned long long"); } + + // https://webidl.spec.whatwg.org/#dfn-numeric-type + bool is_numeric() const { return is_integer() || name.is_one_of("float", "unrestricted float", "double", "unrestricted double"); } +}; + +static CppType idl_type_name_to_cpp_type(Type const& type); + +struct UnionType : public Type { + UnionType() = default; + + UnionType(String name, bool nullable, NonnullRefPtrVector<Type> member_types) + : Type(move(name), nullable) + , member_types(move(member_types)) + { + } + + virtual ~UnionType() override = default; + + NonnullRefPtrVector<Type> member_types; + + // https://webidl.spec.whatwg.org/#dfn-flattened-union-member-types + NonnullRefPtrVector<Type> flattened_member_types() const + { + // 1. Let T be the union type. + + // 2. Initialize S to ā
. + NonnullRefPtrVector<Type> types; + + // 3. For each member type U of T: + for (auto& type : member_types) { + // FIXME: 1. If U is an annotated type, then set U to be the inner type of U. + + // 2. If U is a nullable type, then set U to be the inner type of U. (NOTE: Not necessary as nullable is stored with Type and not as a separate struct) + + // 3. If U is a union type, then add to S the flattened member types of U. + if (is<UnionType>(type)) { + auto& union_member_type = verify_cast<UnionType>(type); + types.extend(union_member_type.flattened_member_types()); + } else { + // 4. Otherwise, U is not a union type. Add U to S. + types.append(type); + } + } + + // 4. Return S. + return types; + } + + // https://webidl.spec.whatwg.org/#dfn-number-of-nullable-member-types + size_t number_of_nullable_member_types() const + { + // 1. Let T be the union type. + + // 2. Initialize n to 0. + size_t num_nullable_member_types = 0; + + // 3. For each member type U of T: + for (auto& type : member_types) { + // 1. If U is a nullable type, then: + if (type.nullable) { + // 1. Set n to n + 1. + ++num_nullable_member_types; + + // 2. Set U to be the inner type of U. (NOTE: Not necessary as nullable is stored with Type and not as a separate struct) + } + + // 2. If U is a union type, then: + if (is<UnionType>(type)) { + auto& union_member_type = verify_cast<UnionType>(type); + + // 1. Let m be the number of nullable member types of U. + // 2. Set n to n + m. + num_nullable_member_types += union_member_type.number_of_nullable_member_types(); + } + } + + // 4. Return n. + return num_nullable_member_types; + } + + // https://webidl.spec.whatwg.org/#dfn-includes-a-nullable-type + bool includes_nullable_type() const + { + // -> the type is a union type and its number of nullable member types is 1. + return number_of_nullable_member_types() == 1; + } + + // -> https://webidl.spec.whatwg.org/#dfn-includes-undefined + bool includes_undefined() const + { + // -> the type is a union type and one of its member types includes undefined. + for (auto& type : member_types) { + if (is<UnionType>(type)) { + auto& union_type = verify_cast<UnionType>(type); + if (union_type.includes_undefined()) + return true; + } + + if (type.name == "undefined"sv) + return true; + } + return false; + } + + String to_variant() const + { + StringBuilder builder; + builder.append("Variant<"); + + auto flattened_types = flattened_member_types(); + for (size_t type_index = 0; type_index < flattened_types.size(); ++type_index) { + auto& type = flattened_types.at(type_index); + + if (type_index > 0) + builder.append(", "); + + auto cpp_type = idl_type_name_to_cpp_type(type); + builder.append(cpp_type.name); + } + + if (includes_undefined()) + builder.append(", Empty"); + + builder.append('>'); + return builder.to_string(); + } }; struct Parameter { @@ -300,6 +429,28 @@ static NonnullOwnPtr<Interface> parse_interface(StringView filename, StringView interface->extended_attributes = parse_extended_attributes(); AK::Function<NonnullRefPtr<Type>()> parse_type = [&]() -> NonnullRefPtr<Type> { + if (lexer.consume_specific('(')) { + NonnullRefPtrVector<Type> union_member_types; + union_member_types.append(parse_type()); + consume_whitespace(); + assert_string("or"); + consume_whitespace(); + union_member_types.append(parse_type()); + consume_whitespace(); + + while (lexer.consume_specific("or")) { + consume_whitespace(); + union_member_types.append(parse_type()); + consume_whitespace(); + } + + assert_specific(')'); + + bool nullable = lexer.consume_specific('?'); + + return adopt_ref(*new UnionType("", nullable, move(union_member_types))); + } + auto consume_name = [&] { return lexer.consume_until([](auto ch) { return !isalnum(ch) && ch != '_'; }); }; @@ -852,6 +1003,11 @@ static CppType idl_type_name_to_cpp_type(Type const& type) return { .name = String::formatted("{}<{}>", storage_type_name, sequence_cpp_type.name), .sequence_storage_type = SequenceStorageType::Vector }; } + if (is<UnionType>(type)) { + auto& union_type = verify_cast<UnionType>(type); + return { .name = union_type.to_variant(), .sequence_storage_type = SequenceStorageType::Vector }; + } + dbgln("Unimplemented type for idl_type_name_to_cpp_type: {}{}", type.name, type.nullable ? "?" : ""); TODO(); } @@ -1052,10 +1208,11 @@ static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter if (!parameter.type->nullable) { scoped_generator.append(R"~~~( String @cpp_name@; - if (@js_name@@js_suffix@.is_null() && @legacy_null_to_empty_string@) + if (@js_name@@js_suffix@.is_null() && @legacy_null_to_empty_string@) { @cpp_name@ = String::empty(); - else + } else { @cpp_name@ = TRY(@js_name@@js_suffix@.to_string(global_object)); + } )~~~"); } else { scoped_generator.append(R"~~~( @@ -1319,6 +1476,360 @@ static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter )~~~"); parameterized_type.generate_sequence_from_iterable(sequence_generator, acceptable_cpp_name, String::formatted("{}{}", js_name, js_suffix), String::formatted("iterator_method{}", recursion_depth), dictionaries, recursion_depth + 1); + } else if (is<IDL::UnionType>(*parameter.type)) { + // https://webidl.spec.whatwg.org/#es-union + + auto union_generator = scoped_generator.fork(); + + auto& union_type = verify_cast<IDL::UnionType>(*parameter.type); + union_generator.set("union_type", union_type.to_variant()); + union_generator.set("recursion_depth", String::number(recursion_depth)); + + // A lambda is used because Variants without "Empty" can't easily be default initialized. + // Plus, this would require the user of union types to always accept a Variant with an Empty type. + + // Additionally, it handles the case of unconditionally throwing a TypeError at the end if none of the types match. + // This is because we cannot unconditionally throw in generate_to_cpp as generate_to_cpp is supposed to assign to a variable and then continue. + // Note that all the other types only throw on a condition. + + // The lambda must take the JS::Value to convert as a parameter instead of capturing it in order to support union types being variadic. + union_generator.append(R"~~~( + auto @js_name@@js_suffix@_to_variant = [&global_object, &vm](JS::Value @js_name@@js_suffix@) -> JS::ThrowCompletionOr<@union_type@> { + // These might be unused. + (void)global_object; + (void)vm; +)~~~"); + + // 1. If the union type includes undefined and V is undefined, then return the unique undefined value. + if (union_type.includes_undefined()) { + scoped_generator.append(R"~~~( + if (@js_name@@js_suffix@.is_undefined()) + return Empty {}; +)~~~"); + } + + // 3. Let types be the flattened member types of the union type. + auto types = union_type.flattened_member_types(); + + bool contains_dictionary_type = false; + for (auto& dictionary : dictionaries) { + for (auto& type : types) { + if (type.name == dictionary.key) { + contains_dictionary_type = true; + break; + } + } + + if (contains_dictionary_type) + break; + } + + // FIXME: 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()) { + TODO(); + } else if (contains_dictionary_type) { + // FIXME: 4. If V is null or undefined, then + // 4.1 If types includes a dictionary type, then return the result of converting V to that dictionary type. + TODO(); + } + + bool includes_object = false; + for (auto& type : types) { + if (type.name == "object") { + includes_object = true; + break; + } + } + + // FIXME: Don't generate this if the union type doesn't include any object types. + union_generator.append(R"~~~( + if (@js_name@@js_suffix@.is_object()) { + [[maybe_unused]] auto& @js_name@@js_suffix@_object = @js_name@@js_suffix@.as_object(); +)~~~"); + + bool includes_wrappable_type = false; + for (auto& type : types) { + if (IDL::is_wrappable_type(type)) { + includes_wrappable_type = true; + break; + } + } + + if (includes_wrappable_type) { + // 5. If V is a platform object, then: + union_generator.append(R"~~~( + if (is<Wrapper>(@js_name@@js_suffix@_object)) { +)~~~"); + + // 1. If types includes an interface type that V implements, then return the IDL value that is a reference to the object V. + for (auto& type : types) { + if (!IDL::is_wrappable_type(type)) + continue; + + auto union_platform_object_type_generator = union_generator.fork(); + union_platform_object_type_generator.set("platform_object_type", String::formatted("{}Wrapper", type.name)); + auto cpp_type = IDL::idl_type_name_to_cpp_type(type); + union_platform_object_type_generator.set("refptr_type", cpp_type.name); + + union_platform_object_type_generator.append(R"~~~( + if (is<@platform_object_type@>(@js_name@@js_suffix@_object)) + return @refptr_type@ { static_cast<@platform_object_type@&>(@js_name@@js_suffix@_object).impl() }; +)~~~"); + } + + // 2. If types includes object, then return the IDL value that is a reference to the object V. + if (includes_object) { + union_generator.append(R"~~~( + return @js_name@@js_suffix@_object; +)~~~"); + } + + union_generator.append(R"~~~( + } +)~~~"); + } + + // FIXME: 6. If Type(V) is Object and V has an [[ArrayBufferData]] internal slot, then + // 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. + + // FIXME: 7. If Type(V) is Object and V has a [[DataView]] internal slot, then: + // 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. + + // FIXME: 8. If Type(V) is Object and V has a [[TypedArrayName]] internal slot, then: + // 1. If types includes a typed array type whose name is the value of Vās [[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. + + // FIXME: 9. If IsCallable(V) is true, then: + // 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. + + // 10. If Type(V) is Object, then: + // 1. If types includes a sequence type, then: + RefPtr<IDL::ParameterizedType> sequence_type; + for (auto& type : types) { + if (type.name == "sequence") { + sequence_type = verify_cast<IDL::ParameterizedType>(type); + break; + } + } + + if (sequence_type) { + // 1. Let method be ? GetMethod(V, @@iterator). + union_generator.append(R"~~~( + auto* method = TRY(@js_name@@js_suffix@.get_method(global_object, *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. + union_generator.append(R"~~~( + if (method) { +)~~~"); + + sequence_type->generate_sequence_from_iterable(union_generator, acceptable_cpp_name, String::formatted("{}{}", js_name, js_suffix), "method", dictionaries, recursion_depth + 1); + + union_generator.append(R"~~~( + + return @cpp_name@; + } +)~~~"); + } + + // FIXME: 2. If types includes a frozen array type, then + // 1. Let method be ? GetMethod(V, @@iterator). + // 2. If method is not undefined, return the result of creating a frozen array of that type from V and method. + + // FIXME: 3. If types includes a dictionary type, then return the result of converting V to that dictionary type. + if (contains_dictionary_type) + TODO(); + + // FIXME: 4. If types includes a record type, then return the result of converting V to that record type. + + // FIXME: 5. If types includes a callback interface type, then return the result of converting V to that callback interface type. + + // 6. If types includes object, then return the IDL value that is a reference to the object V. + if (includes_object) { + union_generator.append(R"~~~( + return @js_name@@js_suffix@_object; +)~~~"); + } + + // End of is_object. + union_generator.append(R"~~~( + } +)~~~"); + + // 11. If Type(V) is Boolean, then: + // 1. If types includes boolean, then return the result of converting V to boolean. + bool includes_boolean = false; + for (auto& type : types) { + if (type.name == "boolean") { + includes_boolean = true; + break; + } + } + + if (includes_boolean) { + union_generator.append(R"~~~( + if (@js_name@@js_suffix@.is_boolean()) + return @js_name@@js_suffix@.as_boolean(); +)~~~"); + } + + RefPtr<IDL::Type> numeric_type; + for (auto& type : types) { + if (type.is_numeric()) { + numeric_type = type; + break; + } + } + + // 12. If Type(V) is Number, then: + // 1. If types includes a numeric type, then return the result of converting V to that numeric type. + if (numeric_type) { + union_generator.append(R"~~~( + if (@js_name@@js_suffix@.is_number()) { +)~~~"); + // NOTE: generate_to_cpp doesn't use the parameter name. + // NOTE: generate_to_cpp will use to_{u32,etc.} which uses to_number internally and will thus use TRY, but it cannot throw as we know we are dealing with a number. + IDL::Parameter parameter { .type = *numeric_type, .name = String::empty(), .optional_default_value = {}, .extended_attributes = {} }; + generate_to_cpp(union_generator, parameter, js_name, js_suffix, String::formatted("{}{}_number", js_name, js_suffix), dictionaries, false, false, {}, false, recursion_depth + 1); + + union_generator.append(R"~~~( + return @js_name@@js_suffix@_number; + } +)~~~"); + } + + // 13. If Type(V) is BigInt, then: + // 1. If types includes bigint, then return the result of converting V to bigint + bool includes_bigint = false; + for (auto& type : types) { + if (type.name == "bigint") { + includes_bigint = true; + break; + } + } + + if (includes_bigint) { + union_generator.append(R"~~~( + if (@js_name@@js_suffix@.is_bigint()) + return @js_name@@js_suffix@.as_bigint(); +)~~~"); + } + + bool includes_string = false; + for (auto& type : types) { + if (type.is_string()) { + includes_string = true; + break; + } + } + + if (includes_string) { + // 14. If types includes a string type, then return the result of converting V to that type. + // NOTE: Currently all string types are converted to String. + union_generator.append(R"~~~( + return TRY(@js_name@@js_suffix@.to_string(global_object)); +)~~~"); + } else if (numeric_type && includes_bigint) { + // 15. If types includes a numeric type and bigint, then return the result of converting V to either that numeric type or bigint. + // https://webidl.spec.whatwg.org/#converted-to-a-numeric-type-or-bigint + // NOTE: This algorithm is only used here. + + // 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). + // 2. If Type(x) is BigInt, then + // 1. Return the IDL bigint value that represents the same numeric value as x. + // 3. Assert: Type(x) is Number. + // 4. Return the result of converting x to T. + + auto union_numeric_type_generator = union_generator.fork(); + auto cpp_type = IDL::idl_type_name_to_cpp_type(*numeric_type); + union_numeric_type_generator.set("numeric_type", cpp_type.name); + + union_numeric_type_generator.append(R"~~~( + auto x = TRY(@js_name@@js_suffix@.to_numeric(global_object)); + if (x.is_bigint()) + return x.as_bigint(); + VERIFY(x.is_number()); +)~~~"); + + // NOTE: generate_to_cpp doesn't use the parameter name. + // NOTE: generate_to_cpp will use to_{u32,etc.} which uses to_number internally and will thus use TRY, but it cannot throw as we know we are dealing with a number. + IDL::Parameter parameter { .type = *numeric_type, .name = String::empty(), .optional_default_value = {}, .extended_attributes = {} }; + generate_to_cpp(union_numeric_type_generator, parameter, "x", String::empty(), "x_number", dictionaries, false, false, {}, false, recursion_depth + 1); + + union_numeric_type_generator.append(R"~~~( + return x_number; +)~~~"); + } else if (numeric_type) { + // 16. If types includes a numeric type, then return the result of converting V to that numeric type. + + // NOTE: generate_to_cpp doesn't use the parameter name. + // NOTE: generate_to_cpp will use to_{u32,etc.} which uses to_number internally and will thus use TRY, but it cannot throw as we know we are dealing with a number. + IDL::Parameter parameter { .type = *numeric_type, .name = String::empty(), .optional_default_value = {}, .extended_attributes = {} }; + generate_to_cpp(union_generator, parameter, js_name, js_suffix, String::formatted("{}{}_number", js_name, js_suffix), dictionaries, false, false, {}, false, recursion_depth + 1); + + union_generator.append(R"~~~( + return @js_name@@js_suffix@_number; +)~~~"); + } else if (includes_boolean) { + // 17. If types includes boolean, then return the result of converting V to boolean. + union_generator.append(R"~~~( + return @js_name@@js_suffix@.to_boolean(); +)~~~"); + } else if (includes_bigint) { + // 18. If types includes bigint, then return the result of converting V to bigint. + union_generator.append(R"~~~( + return TRY(@js_name@@js_suffix@.to_bigint(global_object)); +)~~~"); + } else { + // 19. Throw a TypeError. + // FIXME: Replace the error message with something more descriptive. + union_generator.append(R"~~~( + return vm.throw_completion<JS::TypeError>(global_object, "No union types matched"); +)~~~"); + } + + // Close the lambda and then perform the conversion. + union_generator.append(R"~~~( + }; + + )~~~"); + + if (!variadic) { + if (!optional) { + union_generator.append(R"~~~( + @union_type@ @cpp_name@ = TRY(@js_name@@js_suffix@_to_variant(@js_name@@js_suffix@)); + )~~~"); + } else { + if (!optional_default_value.has_value()) { + union_generator.append(R"~~~( + Optional<@union_type@> @cpp_name@; + if (!@js_name@@js_suffix@.is_undefined()) + @cpp_name@ = TRY(@js_name@@js_suffix@_to_variant(@js_name@@js_suffix@)); + )~~~"); + } else { + if (optional_default_value != "\"\"") + TODO(); + + union_generator.append(R"~~~( + @union_type@ @cpp_name@ = @js_name@@js_suffix@.is_undefined() ? String::empty() : TRY(@js_name@@js_suffix@_to_variant(@js_name@@js_suffix@)); + )~~~"); + } + } + } else { + union_generator.append(R"~~~( + Vector<@union_type@> @cpp_name@; + @cpp_name@.ensure_capacity(vm.argument_count() - @js_suffix@); + + for (size_t i = @js_suffix@; i < vm.argument_count(); ++i) { + auto result = TRY(@js_name@@js_suffix@_to_variant(vm.argument(i))); + @cpp_name@.append(move(result)); + } + )~~~"); + } } else { dbgln("Unimplemented JS-to-C++ conversion: {}", parameter.type->name); VERIFY_NOT_REACHED(); @@ -1506,6 +2017,8 @@ static void generate_wrap_statement(SourceGenerator& generator, String const& va else @result_expression@ @value@.callback.cell(); )~~~"); + } else if (is<IDL::UnionType>(type)) { + TODO(); } else { if (wrapping_reference == WrappingReference::No) { scoped_generator.append(R"~~~( |