mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-22 07:30:19 +00:00
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:
parent
24cf56896b
commit
59e9e7cc61
Notes:
sideshowbarker
2024-07-17 17:53:59 +09:00
Author: https://github.com/IdanHo Commit: https://github.com/SerenityOS/serenity/commit/59e9e7cc61 Pull-request: https://github.com/SerenityOS/serenity/pull/12891
4 changed files with 198 additions and 28 deletions
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(); }
|
||||
|
||||
|
|
|
@ -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") \
|
||||
|
|
Loading…
Reference in a new issue