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.
This commit is contained in:
Idan Horowitz 2022-03-05 20:51:17 +02:00 committed by Andreas Kling
parent 24cf56896b
commit 59e9e7cc61
Notes: sideshowbarker 2024-07-17 17:53:59 +09:00
4 changed files with 198 additions and 28 deletions

View File

@ -10,6 +10,7 @@
#include "IDLTypes.h"
#include <AK/LexicalPath.h>
#include <AK/Queue.h>
#include <AK/QuickSort.h>
Vector<StringView> s_header_search_paths;
@ -1079,21 +1080,21 @@ static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter
}
}
template<typename FunctionType>
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)
{
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()));
if (function.length() == 0)
if (argument_count == 0)
return;
if (function.length() == 1) {
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(argument_count));
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<String> generate_arguments_match_check_for_count(Vector<IDL::Parameter> const& parameters, size_t argument_count)
{
Vector<String> 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<String> 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<JS::TypeError>(global_object, JS::ErrorType::OverloadResolutionFailed);
}
)~~~");
}
void generate_header(IDL::Interface const& interface)
{
StringBuilder builder;
@ -2355,12 +2461,20 @@ private:
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<JS::Object*> @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 @@ public:
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) {

View File

@ -311,7 +311,7 @@ Function Parser::parse_function(HashMap<String, String>& 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<Interface> 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;

View File

@ -78,6 +78,8 @@ struct Function {
String name;
Vector<Parameter> parameters;
HashMap<String, String> 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<Function&> 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<String> imported_paths;
NonnullOwnPtrVector<Interface> imported_modules;
HashMap<String, Vector<Function&>> overload_sets;
HashMap<String, Vector<Function&>> static_overload_sets;
// https://webidl.spec.whatwg.org/#dfn-support-indexed-properties
bool supports_indexed_properties() const { return indexed_property_getter.has_value(); }

View File

@ -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") \