forked from SerenityOS/serenity
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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.
- Loading branch information
1 parent
ebc2984
commit 07a0d9d
Showing
1 changed file
with
250 additions
and
74 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
static Optional<String> generate_arguments_match_check_for_count(Vector<IDL::Parameter> const& parameters, size_t argument_count) | ||
// https://webidl.spec.whatwg.org/#compute-the-effective-overload-set | ||
static EffectiveOverloadSet compute_the_effective_overload_set(auto const& overload_set) | ||
{ | ||
Vector<String> conditions; | ||
for (auto i = 0u; i < argument_count; ++i) { | ||
auto const& parameter = parameters[i]; | ||
if (parameter.type->is_string() || parameter.type->is_primitive()) | ||
continue; | ||
auto argument = String::formatted("arg{}", i); | ||
StringBuilder condition; | ||
condition.append('('); | ||
if (parameter.type->is_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(" && "sv, conditions)); | ||
// 1. Let S be an ordered set. | ||
Vector<EffectiveOverloadSet::Item> overloads; | ||
|
||
// 2. Let F be an ordered set with items as follows, according to the kind of effective overload set: | ||
// Note: This is determined by the caller of generate_overload_arbiter() | ||
|
||
// 3. Let maxarg be the maximum number of arguments the operations, legacy factory functions, or | ||
// callback functions in F are declared to take. For variadic operations and legacy factory functions, | ||
// the argument on which the ellipsis appears counts as a single argument. | ||
auto overloaded_functions = overload_set.value; | ||
auto maximum_arguments = 0; | ||
for (auto const& function : overloaded_functions) | ||
maximum_arguments = max(maximum_arguments, static_cast<int>(function.parameters.size())); | ||
|
||
// 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++; | ||
} | ||
|
||
return EffectiveOverloadSet { move(overloads) }; | ||
} | ||
|
||
static String generate_arguments_match_check(Function const& function) | ||
static String generate_constructor_for_idl_type(Type const& type) | ||
{ | ||
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()); | ||
auto append_type_list = [](auto& builder, auto const& type_list) { | ||
bool first = true; | ||
for (auto const& child_type : type_list) { | ||
if (first) { | ||
first = false; | ||
} else { | ||
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(); | ||
} | ||
case Type::Kind::Union: { | ||
auto const& union_type = type.as_union(); | ||
StringBuilder builder; | ||
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(); | ||
} | ||
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(" || "sv, options); | ||
|
||
VERIFY_NOT_REACHED(); | ||
} | ||
|
||
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"~~~( | ||
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); | ||
generate_argument_count_check(function_generator, overload_set.key, minimum_argument_count); | ||
auto all_possible_effective_overloads = compute_the_effective_overload_set(overload_set); | ||
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; | ||
quick_sort(overloaded_functions, [](auto const& a, auto const& b) { return a.shortest_length() < b.shortest_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.parameters.size(); | ||
|
||
function_generator.set("argument_count", String::number(argument_count)); | ||
auto arguments_match_check = generate_arguments_match_check(overloaded_function); | ||
function_generator.set("arguments_match_check", arguments_match_check); | ||
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 [email protected]@ = vm.argument(@argument.index@); | ||
)~~~"); | ||
} | ||
fetched_arguments = argument_count; | ||
// Generate the effective overload set for each argument count. | ||
// This skips part of the Overload Resolution Algorithm https://webidl.spec.whatwg.org/#es-overloads | ||
// Namely, since that discards any overloads that don't have the exact number of arguments that were given, | ||
// we simply only provide the overloads that do have that number of arguments. | ||
for (auto argument_count = 0u; argument_count <= maximum_argument_count; ++argument_count) { | ||
// FIXME: Calculate the distinguishing argument index now instead of at runtime. | ||
|
||
auto effective_overload_count = 0; | ||
for (auto const& overload : overloads_set) { | ||
if (overload.types.size() == argument_count) | ||
effective_overload_count++; | ||
} | ||
|
||
auto is_last = i == overloaded_functions.size() - 1; | ||
if (!is_last) { | ||
function_generator.append(R"~~~( | ||
if (vm.argument_count() == @argument_count@) { | ||
if (effective_overload_count == 0) | ||
continue; | ||
|
||
function_generator.set("current_argument_count", String::number(argument_count)); | ||
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; | ||
} | ||
} | ||
|
||
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@);"); | ||
} | ||
|
||
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); | ||
function_generator.append(R"~~~( | ||
effective_overload_set.emplace(move(overloads)); | ||
break; | ||
} | ||
)~~~"); | ||
} | ||
} | ||
|
||
if (!is_last) { | ||
function_generator.append(R"~~~( | ||
function_generator.append(R"~~~( | ||
} | ||
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"~~~( | ||
return vm.throw_completion<JS::TypeError>(JS::ErrorType::OverloadResolutionFailed); | ||
default: | ||
VERIFY_NOT_REACHED(); | ||
} | ||
} | ||
)~~~"); | ||
} | ||
|
@@ -2296,6 +2470,7 @@ void generate_prototype_implementation(IDL::Interface const& interface) | |
|
||
generator.append(R"~~~( | ||
#include <AK/Function.h> | ||
#include <LibIDL/Types.h> | ||
#include <LibJS/Runtime/Array.h> | ||
#include <LibJS/Runtime/DataView.h> | ||
#include <LibJS/Runtime/Error.h> | ||
|
@@ -2306,6 +2481,7 @@ void generate_prototype_implementation(IDL::Interface const& interface) | |
#include <LibJS/Runtime/Value.h> | ||
#include <LibWeb/Bindings/@[email protected]> | ||
#include <LibWeb/Bindings/ExceptionOrUtils.h> | ||
#include <LibWeb/Bindings/IDLOverloadResolution.h> | ||
#include <LibWeb/Bindings/LocationObject.h> | ||
#include <LibWeb/DOM/Element.h> | ||
#include <LibWeb/DOM/Event.h> | ||
|