From 59e9e7cc615a9bdbefa38362528759398917e174 Mon Sep 17 00:00:00 2001 From: Idan Horowitz Date: Sat, 5 Mar 2022 20:51:17 +0200 Subject: [PATCH] LibWeb: Add a very basic and ad-hoc version of IDL overload resolution This initial version lays down the basic foundation of IDL overload resolution, but much of it will have to be replaced with the actual IDL overload resolution algorithms once we start implementing more complex IDL overloading scenarios. --- .../LibWeb/WrapperGenerator/IDLGenerators.cpp | 183 +++++++++++++++--- .../LibWeb/WrapperGenerator/IDLParser.cpp | 27 ++- .../LibWeb/WrapperGenerator/IDLTypes.h | 13 ++ Userland/Libraries/LibJS/Runtime/ErrorTypes.h | 1 + 4 files changed, 197 insertions(+), 27 deletions(-) diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/WrapperGenerator/IDLGenerators.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/WrapperGenerator/IDLGenerators.cpp index a0d1c6b7e82b77..bddc83e74d4dc6 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/WrapperGenerator/IDLGenerators.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/WrapperGenerator/IDLGenerators.cpp @@ -10,6 +10,7 @@ #include "IDLTypes.h" #include #include +#include Vector s_header_search_paths; @@ -1079,21 +1080,21 @@ static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter } } -template -static void generate_argument_count_check(SourceGenerator& generator, FunctionType& function) +static void generate_argument_count_check(SourceGenerator& generator, String const& function_name, size_t argument_count) { + if (argument_count == 0) + return; + auto argument_count_check_generator = generator.fork(); - argument_count_check_generator.set("function.name", function.name); - argument_count_check_generator.set("function.nargs", String::number(function.length())); + argument_count_check_generator.set("function.name", function_name); + argument_count_check_generator.set("function.nargs", String::number(argument_count)); - if (function.length() == 0) - return; - if (function.length() == 1) { + if (argument_count == 1) { argument_count_check_generator.set(".bad_arg_count", "JS::ErrorType::BadArgCountOne"); argument_count_check_generator.set(".arg_count_suffix", ""); } else { argument_count_check_generator.set(".bad_arg_count", "JS::ErrorType::BadArgCountMany"); - argument_count_check_generator.set(".arg_count_suffix", String::formatted(", \"{}\"", function.length())); + argument_count_check_generator.set(".arg_count_suffix", String::formatted(", \"{}\"", argument_count)); } argument_count_check_generator.append(R"~~~( @@ -1316,6 +1317,7 @@ static void generate_function(SourceGenerator& generator, IDL::Function const& f function_generator.set("interface_fully_qualified_name", interface_fully_qualified_name); function_generator.set("function.name", function.name); function_generator.set("function.name:snakecase", make_input_acceptable_cpp(function.name.to_snakecase())); + function_generator.set("overload_suffix", function.is_overloaded ? String::number(function.overload_index) : String::empty()); if (function.extended_attributes.contains("ImplementedAs")) { auto implemented_as = function.extended_attributes.get("ImplementedAs").value(); @@ -1325,7 +1327,7 @@ static void generate_function(SourceGenerator& generator, IDL::Function const& f } function_generator.append(R"~~~( -JS_DEFINE_NATIVE_FUNCTION(@class_name@::@function.name:snakecase@) +JS_DEFINE_NATIVE_FUNCTION(@class_name@::@function.name:snakecase@@overload_suffix@) { )~~~"); @@ -1335,7 +1337,9 @@ JS_DEFINE_NATIVE_FUNCTION(@class_name@::@function.name:snakecase@) )~~~"); } - generate_argument_count_check(generator, function); + // Optimization: overloaded functions' arguments count is checked by the overload arbiter + if (!function.is_overloaded) + generate_argument_count_check(generator, function.name, function.length()); StringBuilder arguments_builder; generate_arguments(generator, function.parameters, arguments_builder, interface); @@ -1358,6 +1362,108 @@ JS_DEFINE_NATIVE_FUNCTION(@class_name@::@function.name:snakecase@) )~~~"); } +// FIXME: This is extremely ad-hoc, implement the WebIDL overload resolution algorithm instead +static Optional generate_arguments_match_check_for_count(Vector const& parameters, size_t argument_count) +{ + Vector conditions; + for (auto i = 0u; i < argument_count; ++i) { + auto const& parameter = parameters[i]; + if (parameter.type->is_string() || parameter.type->is_numeric()) + continue; + auto argument = String::formatted("arg{}", i); + StringBuilder condition; + condition.append('('); + if (parameter.type->nullable) + condition.appendff("{}.is_nullish() || ", argument); + else if (parameter.optional) + condition.appendff("{}.is_undefined() || ", argument); + condition.appendff("{}.is_object()", argument); + condition.append(')'); + conditions.append(condition.build()); + } + if (conditions.is_empty()) + return {}; + return String::formatted("({})", String::join(" && ", conditions)); +} + +static String generate_arguments_match_check(Function const& function) +{ + Vector options; + for (size_t i = 0; i < function.parameters.size(); ++i) { + if (!function.parameters[i].optional && !function.parameters[i].variadic) + continue; + auto match_check = generate_arguments_match_check_for_count(function.parameters, i); + if (match_check.has_value()) + options.append(match_check.release_value()); + } + if (!function.parameters.is_empty() && !function.parameters.last().variadic) { + auto match_check = generate_arguments_match_check_for_count(function.parameters, function.parameters.size()); + if (match_check.has_value()) + options.append(match_check.release_value()); + } + return String::join(" || ", options); +} + +static void generate_overload_arbiter(SourceGenerator& generator, auto const& overload_set, String const& class_name) +{ + auto function_generator = generator.fork(); + function_generator.set("class_name", class_name); + function_generator.set("function.name:snakecase", make_input_acceptable_cpp(overload_set.key.to_snakecase())); + + function_generator.append(R"~~~( +JS_DEFINE_NATIVE_FUNCTION(@class_name@::@function.name:snakecase@) +{ +)~~~"); + + auto minimum_argument_count = get_shortest_function_length(overload_set.value); + generate_argument_count_check(function_generator, overload_set.key, minimum_argument_count); + + auto overloaded_functions = overload_set.value; + quick_sort(overloaded_functions, [](auto const& a, auto const& b) { return a.length() < b.length(); }); + auto fetched_arguments = 0u; + for (auto i = 0u; i < overloaded_functions.size(); ++i) { + auto const& overloaded_function = overloaded_functions[i]; + auto argument_count = overloaded_function.length(); + + function_generator.set("argument_count", String::number(argument_count)); + function_generator.set("arguments_match_check", generate_arguments_match_check(overloaded_function)); + function_generator.set("overload_suffix", String::number(i)); + + if (argument_count > fetched_arguments) { + for (auto j = fetched_arguments; j < argument_count; ++j) { + function_generator.set("argument.index", String::number(j)); + function_generator.append(R"~~~( + [[maybe_unused]] auto arg@argument.index@ = vm.argument(@argument.index@); +)~~~"); + } + fetched_arguments = argument_count; + } + + auto is_last = i == overloaded_functions.size() - 1; + if (!is_last) { + function_generator.append(R"~~~( + if (vm.argument_count() == @argument_count@) { +)~~~"); + } + + function_generator.append(R"~~~( + if (@arguments_match_check@) + return @function.name:snakecase@@overload_suffix@(vm, global_object); +)~~~"); + + if (!is_last) { + function_generator.append(R"~~~( + } +)~~~"); + } + } + + function_generator.append(R"~~~( + return vm.throw_completion(global_object, JS::ErrorType::OverloadResolutionFailed); +} +)~~~"); +} + void generate_header(IDL::Interface const& interface) { StringBuilder builder; @@ -2355,12 +2461,20 @@ class @constructor_class@ : public JS::NativeFunction { virtual bool has_constructor() const override { return true; } )~~~"); - for (auto& function : interface.static_functions) { + for (auto const& overload_set : interface.static_overload_sets) { auto function_generator = generator.fork(); - function_generator.set("function.name:snakecase", make_input_acceptable_cpp(function.name.to_snakecase())); + function_generator.set("function.name:snakecase", make_input_acceptable_cpp(overload_set.key.to_snakecase())); function_generator.append(R"~~~( JS_DECLARE_NATIVE_FUNCTION(@function.name:snakecase@); )~~~"); + if (overload_set.value.size() > 1) { + for (auto i = 0u; i < overload_set.value.size(); ++i) { + function_generator.set("overload_suffix", String::number(i)); + function_generator.append(R"~~~( + JS_DECLARE_NATIVE_FUNCTION(@function.name:snakecase@@overload_suffix@); +)~~~"); + } + } } generator.append(R"~~~( @@ -2486,7 +2600,7 @@ JS::ThrowCompletionOr @constructor_class@::construct(FunctionObject )~~~"); if (!constructor.parameters.is_empty()) { - generate_argument_count_check(generator, constructor); + generate_argument_count_check(generator, constructor.name, constructor.length()); StringBuilder arguments_builder; generate_arguments(generator, constructor.parameters, arguments_builder, interface); @@ -2534,11 +2648,11 @@ define_direct_property("@constant.name@", JS::Value((i32)@constant.value@), JS:: } // https://webidl.spec.whatwg.org/#es-operations - for (auto& function : interface.static_functions) { + for (auto const& overload_set : interface.static_overload_sets) { auto function_generator = generator.fork(); - function_generator.set("function.name", function.name); - function_generator.set("function.name:snakecase", make_input_acceptable_cpp(function.name.to_snakecase())); - function_generator.set("function.length", String::number(function.length())); + function_generator.set("function.name", overload_set.key); + function_generator.set("function.name:snakecase", make_input_acceptable_cpp(overload_set.key.to_snakecase())); + function_generator.set("function.length", String::number(get_shortest_function_length(overload_set.value))); function_generator.append(R"~~~( define_native_function("@function.name@", @function.name:snakecase@, @function.length@, default_attributes); @@ -2550,8 +2664,12 @@ define_direct_property("@constant.name@", JS::Value((i32)@constant.value@), JS:: )~~~"); // Implementation: Static Functions - for (auto& function : interface.static_functions) { + for (auto& function : interface.static_functions) generate_function(generator, function, StaticFunction::Yes, interface.constructor_class, interface.fully_qualified_name, interface); + for (auto const& overload_set : interface.static_overload_sets) { + if (overload_set.value.size() == 1) + continue; + generate_overload_arbiter(generator, overload_set, interface.constructor_class); } generator.append(R"~~~( @@ -2587,12 +2705,20 @@ class @prototype_class@ : public JS::Object { private: )~~~"); - for (auto& function : interface.functions) { + for (auto const& overload_set : interface.overload_sets) { auto function_generator = generator.fork(); - function_generator.set("function.name:snakecase", make_input_acceptable_cpp(function.name.to_snakecase())); + function_generator.set("function.name:snakecase", make_input_acceptable_cpp(overload_set.key.to_snakecase())); function_generator.append(R"~~~( JS_DECLARE_NATIVE_FUNCTION(@function.name:snakecase@); )~~~"); + if (overload_set.value.size() > 1) { + for (auto i = 0u; i < overload_set.value.size(); ++i) { + function_generator.set("overload_suffix", String::number(i)); + function_generator.append(R"~~~( + JS_DECLARE_NATIVE_FUNCTION(@function.name:snakecase@@overload_suffix@); +)~~~"); + } + } } if (interface.has_stringifier) { @@ -2788,13 +2914,14 @@ void @prototype_class@::initialize(JS::GlobalObject& global_object) } // https://webidl.spec.whatwg.org/#es-operations - for (auto& function : interface.functions) { + for (auto const& overload_set : interface.overload_sets) { auto function_generator = generator.fork(); - function_generator.set("function.name", function.name); - function_generator.set("function.name:snakecase", make_input_acceptable_cpp(function.name.to_snakecase())); - function_generator.set("function.length", String::number(function.length())); + function_generator.set("function.name", overload_set.key); + function_generator.set("function.name:snakecase", make_input_acceptable_cpp(overload_set.key.to_snakecase())); + function_generator.set("function.length", String::number(get_shortest_function_length(overload_set.value))); - if (function.extended_attributes.contains("Unscopable")) { + // FIXME: What if only some of the overloads are Unscopable? + if (any_of(overload_set.value, [](auto const& function) { return function.extended_attributes.contains("Unscopable"); })) { function_generator.append(R"~~~( MUST(unscopable_object->create_data_property("@function.name@", JS::Value(true))); )~~~"); @@ -2971,8 +3098,12 @@ JS_DEFINE_NATIVE_FUNCTION(@prototype_class@::@attribute.setter_callback@) } // Implementation: Functions - for (auto& function : interface.functions) { + for (auto& function : interface.functions) generate_function(generator, function, StaticFunction::No, interface.prototype_class, interface.fully_qualified_name, interface); + for (auto const& overload_set : interface.overload_sets) { + if (overload_set.value.size() == 1) + continue; + generate_overload_arbiter(generator, overload_set, interface.prototype_class); } if (interface.has_stringifier) { diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/WrapperGenerator/IDLParser.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/WrapperGenerator/IDLParser.cpp index 151e36d94269b9..55ab5c3b439cdd 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/WrapperGenerator/IDLParser.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/WrapperGenerator/IDLParser.cpp @@ -311,7 +311,7 @@ Function Parser::parse_function(HashMap& extended_attributes, In consume_whitespace(); assert_specific(';'); - Function function { move(return_type), name, move(parameters), move(extended_attributes) }; + Function function { move(return_type), name, move(parameters), move(extended_attributes), {}, false }; // "Defining a special operation with an identifier is equivalent to separating the special operation out into its own declaration without an identifier." if (is_special_operation == IsSpecialOperation::No || (is_special_operation == IsSpecialOperation::Yes && !name.is_empty())) { @@ -803,6 +803,31 @@ NonnullOwnPtr Parser::parse() } } + // Create overload sets + for (auto& function : interface->functions) { + auto& overload_set = interface->overload_sets.ensure(function.name); + function.overload_index = overload_set.size(); + overload_set.append(function); + } + for (auto& overload_set : interface->overload_sets) { + if (overload_set.value.size() == 1) + continue; + for (auto& overloaded_function : overload_set.value) + overloaded_function.is_overloaded = true; + } + for (auto& function : interface->static_functions) { + auto& overload_set = interface->static_overload_sets.ensure(function.name); + function.overload_index = overload_set.size(); + overload_set.append(function); + } + for (auto& overload_set : interface->static_overload_sets) { + if (overload_set.value.size() == 1) + continue; + for (auto& overloaded_function : overload_set.value) + overloaded_function.is_overloaded = true; + } + // FIXME: Add support for overloading constructors + interface->imported_modules = move(imports); return interface; diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/WrapperGenerator/IDLTypes.h b/Meta/Lagom/Tools/CodeGenerators/LibWeb/WrapperGenerator/IDLTypes.h index dcc1312b6c3af3..f92d3b0b1ae0c4 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/WrapperGenerator/IDLTypes.h +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/WrapperGenerator/IDLTypes.h @@ -78,6 +78,8 @@ struct Function { String name; Vector parameters; HashMap extended_attributes; + size_t overload_index { 0 }; + bool is_overloaded { false }; size_t length() const { return get_function_length(*this); } }; @@ -144,6 +146,14 @@ struct ParameterizedType : public Type { void generate_sequence_from_iterable(SourceGenerator& generator, String const& cpp_name, String const& iterable_cpp_name, String const& iterator_method_cpp_name, IDL::Interface const&, size_t recursion_depth) const; }; +static inline size_t get_shortest_function_length(Vector const& overload_set) +{ + size_t longest_length = SIZE_MAX; + for (auto const& function : overload_set) + longest_length = min(function.length(), longest_length); + return longest_length; +} + struct Interface { String name; String parent_name; @@ -189,6 +199,9 @@ struct Interface { HashTable imported_paths; NonnullOwnPtrVector imported_modules; + HashMap> overload_sets; + HashMap> static_overload_sets; + // https://webidl.spec.whatwg.org/#dfn-support-indexed-properties bool supports_indexed_properties() const { return indexed_property_getter.has_value(); } diff --git a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h index 74c8f38b7e00ae..3086483b7b661a 100644 --- a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h +++ b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h @@ -102,6 +102,7 @@ "Object prototype must not be {} on a super property access") \ M(ObjectPrototypeWrongType, "Prototype must be an object or null") \ M(OptionIsNotValidValue, "{} is not a valid value for option {}") \ + M(OverloadResolutionFailed, "Overload resolution failed") \ M(PrivateFieldAlreadyDeclared, "Private field '{}' has already been declared") \ M(PrivateFieldDoesNotExistOnObject, "Private field '{}' does not exist on object") \ M(PrivateFieldGetAccessorWithoutGetter, "Cannot get private field '{}' as accessor without getter") \