1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-27 00:47:45 +00:00

WrapperGenerator: Use the new shiny Overload Resolution Algorithm :^)

We compute the effective overload sets for each argument count at build
time, to save us having to do it every time a function with overloads
is called.
This commit is contained in:
Sam Atkins 2022-09-08 17:44:06 +01:00 committed by Andreas Kling
parent ebc29842c8
commit 07a0d9df30

View file

@ -1784,46 +1784,170 @@ JS_DEFINE_NATIVE_FUNCTION(@class_name@::@function.name:snakecase@@overload_suffi
)~~~"); )~~~");
} }
// FIXME: This is extremely ad-hoc, implement the WebIDL overload resolution algorithm instead // https://webidl.spec.whatwg.org/#compute-the-effective-overload-set
static Optional<String> generate_arguments_match_check_for_count(Vector<IDL::Parameter> const& parameters, size_t argument_count) static EffectiveOverloadSet compute_the_effective_overload_set(auto const& overload_set)
{ {
Vector<String> conditions; // 1. Let S be an ordered set.
for (auto i = 0u; i < argument_count; ++i) { Vector<EffectiveOverloadSet::Item> overloads;
auto const& parameter = parameters[i];
if (parameter.type->is_string() || parameter.type->is_primitive()) // 2. Let F be an ordered set with items as follows, according to the kind of effective overload set:
continue; // Note: This is determined by the caller of generate_overload_arbiter()
auto argument = String::formatted("arg{}", i);
StringBuilder condition; // 3. Let maxarg be the maximum number of arguments the operations, legacy factory functions, or
condition.append('('); // callback functions in F are declared to take. For variadic operations and legacy factory functions,
if (parameter.type->is_nullable()) // the argument on which the ellipsis appears counts as a single argument.
condition.appendff("{}.is_nullish() || ", argument); auto overloaded_functions = overload_set.value;
else if (parameter.optional) auto maximum_arguments = 0;
condition.appendff("{}.is_undefined() || ", argument); for (auto const& function : overloaded_functions)
condition.appendff("{}.is_object()", argument); maximum_arguments = max(maximum_arguments, static_cast<int>(function.parameters.size()));
condition.append(')');
conditions.append(condition.build()); // 4. Let max be max(maxarg, N).
// NOTE: We don't do this step. `N` is a runtime value, so we just use `maxarg` here instead.
// Later, `generate_overload_arbiter()` produces individual overload sets for each possible N.
// 5. For each operation or extended attribute X in F:
auto overload_id = 0;
for (auto const& overload : overloaded_functions) {
// 1. Let arguments be the list of arguments X is declared to take.
auto const& arguments = overload.parameters;
// 2. Let n be the size of arguments.
int argument_count = (int)arguments.size();
// 3. Let types be a type list.
NonnullRefPtrVector<Type> types;
// 4. Let optionalityValues be an optionality list.
Vector<Optionality> optionality_values;
bool overload_is_variadic = false;
// 5. For each argument in arguments:
for (auto const& argument : arguments) {
// 1. Append the type of argument to types.
types.append(argument.type);
// 2. Append "variadic" to optionalityValues if argument is a final, variadic argument, "optional" if argument is optional, and "required" otherwise.
if (argument.variadic) {
optionality_values.append(Optionality::Variadic);
overload_is_variadic = true;
} else if (argument.optional) {
optionality_values.append(Optionality::Optional);
} else {
optionality_values.append(Optionality::Required);
}
}
// 6. Append the tuple (X, types, optionalityValues) to S.
overloads.empend(overload_id, types, optionality_values);
// 7. If X is declared to be variadic, then:
if (overload_is_variadic) {
// 1. For each i in the range n to max 1, inclusive:
for (auto i = argument_count; i < maximum_arguments; ++i) {
// 1. Let t be a type list.
// 2. Let o be an optionality list.
// NOTE: We hold both of these in an Item instead.
EffectiveOverloadSet::Item item;
item.callable_id = overload_id;
// 3. For each j in the range 0 to n 1, inclusive:
for (auto j = 0; j < argument_count; ++j) {
// 1. Append types[j] to t.
item.types.append(types[j]);
// 2. Append optionalityValues[j] to o.
item.optionality_values.append(optionality_values[j]);
}
// 4. For each j in the range n to i, inclusive:
for (auto j = argument_count; j <= i; ++j) {
// 1. Append types[n 1] to t.
item.types.append(types[argument_count - 1]);
// 2. Append "variadic" to o.
item.optionality_values.append(Optionality::Variadic);
}
// 5. Append the tuple (X, t, o) to S.
overloads.append(move(item));
}
}
// 8. Let i be n 1.
auto i = argument_count - 1;
// 9. While i ≥ 0:
while (i >= 0) {
// 1. If arguments[i] is not optional (i.e., it is not marked as "optional" and is not a final, variadic argument), then break.
if (!arguments[i].optional && !arguments[i].variadic)
break;
// 2. Let t be a type list.
// 3. Let o be an optionality list.
// NOTE: We hold both of these in an Item instead.
EffectiveOverloadSet::Item item;
item.callable_id = overload_id;
// 4. For each j in the range 0 to i 1, inclusive:
for (auto j = 0; j < i; ++j) {
// 1. Append types[j] to t.
item.types.append(types[j]);
// 2. Append optionalityValues[j] to o.
item.optionality_values.append(optionality_values[j]);
}
// 5. Append the tuple (X, t, o) to S.
overloads.append(move(item));
// 6. Set i to i 1.
--i;
}
overload_id++;
} }
if (conditions.is_empty())
return {}; return EffectiveOverloadSet { move(overloads) };
return String::formatted("({})", String::join(" && "sv, conditions));
} }
static String generate_arguments_match_check(Function const& function) static String generate_constructor_for_idl_type(Type const& type)
{ {
Vector<String> options; auto append_type_list = [](auto& builder, auto const& type_list) {
for (size_t i = 0; i < function.parameters.size(); ++i) { bool first = true;
if (!function.parameters[i].optional && !function.parameters[i].variadic) for (auto const& child_type : type_list) {
continue; if (first) {
auto match_check = generate_arguments_match_check_for_count(function.parameters, i); first = false;
if (match_check.has_value()) } else {
options.append(match_check.release_value()); builder.append(", "sv);
}
builder.append(generate_constructor_for_idl_type(child_type));
}
};
switch (type.kind()) {
case Type::Kind::Plain:
return String::formatted("make_ref_counted<IDL::Type>(\"{}\", {})", type.name(), type.is_nullable());
case Type::Kind::Parameterized: {
auto const& parameterized_type = type.as_parameterized();
StringBuilder builder;
builder.appendff("make_ref_counted<IDL::ParameterizedTypeType>(\"{}\", {}, NonnullRefPtrVector<IDL::Type> {{", type.name(), type.is_nullable());
append_type_list(builder, parameterized_type.parameters());
builder.append("})"sv);
return builder.to_string();
} }
if (!function.parameters.is_empty() && !function.parameters.last().variadic) { case Type::Kind::Union: {
auto match_check = generate_arguments_match_check_for_count(function.parameters, function.parameters.size()); auto const& union_type = type.as_union();
if (match_check.has_value()) StringBuilder builder;
options.append(match_check.release_value()); builder.appendff("make_ref_counted<IDL::UnionType>(\"{}\", {}, NonnullRefPtrVector<IDL::Type> {{", type.name(), type.is_nullable());
append_type_list(builder, union_type.member_types());
builder.append("})"sv);
return builder.to_string();
} }
return String::join(" || "sv, options); }
VERIFY_NOT_REACHED();
} }
static void generate_overload_arbiter(SourceGenerator& generator, auto const& overload_set, String const& class_name) static void generate_overload_arbiter(SourceGenerator& generator, auto const& overload_set, String const& class_name)
@ -1835,61 +1959,111 @@ static void generate_overload_arbiter(SourceGenerator& generator, auto const& ov
function_generator.append(R"~~~( function_generator.append(R"~~~(
JS_DEFINE_NATIVE_FUNCTION(@class_name@::@function.name:snakecase@) JS_DEFINE_NATIVE_FUNCTION(@class_name@::@function.name:snakecase@)
{ {
[[maybe_unused]] auto& realm = *vm.current_realm(); Optional<IDL::EffectiveOverloadSet> effective_overload_set;
)~~~"); )~~~");
auto minimum_argument_count = get_shortest_function_length(overload_set.value); auto all_possible_effective_overloads = compute_the_effective_overload_set(overload_set);
generate_argument_count_check(function_generator, overload_set.key, minimum_argument_count); auto overloads_set = all_possible_effective_overloads.items();
auto maximum_argument_count = 0u;
for (auto const& overload : overloads_set)
maximum_argument_count = max(maximum_argument_count, overload.types.size());
function_generator.set("max_argument_count", String::number(maximum_argument_count));
function_generator.appendln(" switch (min(@max_argument_count@, vm.argument_count())) {");
auto overloaded_functions = overload_set.value; // Generate the effective overload set for each argument count.
quick_sort(overloaded_functions, [](auto const& a, auto const& b) { return a.shortest_length() < b.shortest_length(); }); // This skips part of the Overload Resolution Algorithm https://webidl.spec.whatwg.org/#es-overloads
auto fetched_arguments = 0u; // Namely, since that discards any overloads that don't have the exact number of arguments that were given,
for (auto i = 0u; i < overloaded_functions.size(); ++i) { // we simply only provide the overloads that do have that number of arguments.
auto const& overloaded_function = overloaded_functions[i]; for (auto argument_count = 0u; argument_count <= maximum_argument_count; ++argument_count) {
auto argument_count = overloaded_function.parameters.size(); // FIXME: Calculate the distinguishing argument index now instead of at runtime.
function_generator.set("argument_count", String::number(argument_count)); auto effective_overload_count = 0;
auto arguments_match_check = generate_arguments_match_check(overloaded_function); for (auto const& overload : overloads_set) {
function_generator.set("arguments_match_check", arguments_match_check); if (overload.types.size() == argument_count)
function_generator.set("overload_suffix", String::number(i)); effective_overload_count++;
}
if (argument_count > fetched_arguments) { if (effective_overload_count == 0)
for (auto j = fetched_arguments; j < argument_count; ++j) { continue;
function_generator.set("argument.index", String::number(j));
function_generator.append(R"~~~( function_generator.set("current_argument_count", String::number(argument_count));
[[maybe_unused]] auto arg@argument.index@ = vm.argument(@argument.index@); function_generator.set("overload_count", String::number(effective_overload_count));
function_generator.appendln(R"~~~(
case @current_argument_count@: {
Vector<IDL::EffectiveOverloadSet::Item> overloads;
overloads.ensure_capacity(@overload_count@);
)~~~"); )~~~");
for (auto& overload : overloads_set) {
if (overload.types.size() != argument_count)
continue;
StringBuilder types_builder;
types_builder.append("NonnullRefPtrVector<IDL::Type> { "sv);
StringBuilder optionality_builder;
optionality_builder.append("Vector<IDL::Optionality> { "sv);
for (auto i = 0u; i < overload.types.size(); ++i) {
if (i > 0) {
types_builder.append(", "sv);
optionality_builder.append(", "sv);
}
types_builder.append(generate_constructor_for_idl_type(overload.types[i]));
optionality_builder.append("IDL::Optionality::"sv);
switch (overload.optionality_values[i]) {
case Optionality::Required:
optionality_builder.append("Required"sv);
break;
case Optionality::Optional:
optionality_builder.append("Optional"sv);
break;
case Optionality::Variadic:
optionality_builder.append("Variadic"sv);
break;
}
} }
fetched_arguments = argument_count;
types_builder.append("}"sv);
optionality_builder.append("}"sv);
function_generator.set("overload.callable_id", String::number(overload.callable_id));
function_generator.set("overload.types", types_builder.to_string());
function_generator.set("overload.optionality_values", optionality_builder.to_string());
function_generator.appendln(" overloads.empend(@overload.callable_id@, @overload.types@, @overload.optionality_values@);");
} }
auto is_last = i == overloaded_functions.size() - 1; function_generator.append(R"~~~(
if (!is_last) { effective_overload_set.emplace(move(overloads));
function_generator.append(R"~~~( break;
if (vm.argument_count() == @argument_count@) {
)~~~");
}
if (arguments_match_check.is_empty()) {
function_generator.append(R"~~~(
return @function.name:snakecase@@overload_suffix@(vm);
)~~~");
} else {
function_generator.append(R"~~~(
if (@arguments_match_check@)
return @function.name:snakecase@@overload_suffix@(vm);
)~~~");
}
if (!is_last) {
function_generator.append(R"~~~(
} }
)~~~"); )~~~");
}
} }
function_generator.append(R"~~~( function_generator.append(R"~~~(
return vm.throw_completion<JS::TypeError>(JS::ErrorType::OverloadResolutionFailed); }
if (!effective_overload_set.has_value())
return vm.throw_completion<JS::TypeError>(JS::ErrorType::OverloadResolutionFailed);
auto chosen_overload = TRY(resolve_overload(vm, effective_overload_set.value()));
switch (chosen_overload.callable_id) {
)~~~");
for (auto i = 0u; i < overload_set.value.size(); ++i) {
function_generator.set("overload_id", String::number(i));
function_generator.append(R"~~~(
case @overload_id@:
return @function.name:snakecase@@overload_id@(vm);
)~~~");
}
function_generator.append(R"~~~(
default:
VERIFY_NOT_REACHED();
}
} }
)~~~"); )~~~");
} }
@ -2296,6 +2470,7 @@ void generate_prototype_implementation(IDL::Interface const& interface)
generator.append(R"~~~( generator.append(R"~~~(
#include <AK/Function.h> #include <AK/Function.h>
#include <LibIDL/Types.h>
#include <LibJS/Runtime/Array.h> #include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/DataView.h> #include <LibJS/Runtime/DataView.h>
#include <LibJS/Runtime/Error.h> #include <LibJS/Runtime/Error.h>
@ -2306,6 +2481,7 @@ void generate_prototype_implementation(IDL::Interface const& interface)
#include <LibJS/Runtime/Value.h> #include <LibJS/Runtime/Value.h>
#include <LibWeb/Bindings/@prototype_class@.h> #include <LibWeb/Bindings/@prototype_class@.h>
#include <LibWeb/Bindings/ExceptionOrUtils.h> #include <LibWeb/Bindings/ExceptionOrUtils.h>
#include <LibWeb/Bindings/IDLOverloadResolution.h>
#include <LibWeb/Bindings/LocationObject.h> #include <LibWeb/Bindings/LocationObject.h>
#include <LibWeb/DOM/Element.h> #include <LibWeb/DOM/Element.h>
#include <LibWeb/DOM/Event.h> #include <LibWeb/DOM/Event.h>