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:
parent
ebc29842c8
commit
07a0d9df30
1 changed files with 250 additions and 74 deletions
|
@ -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>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue