ladybird/Meta/Generators/libweb_bindings/context.py
Shannon Booth dba62aefc0 LibWeb/Bindings: Port IDL bindings generator to Python
Replace the Lagom C++ bindings generator invocation with the new Python
generator under Meta/Generators/libweb_bindings.

Fold the exposed-interface generation into the same generator entry
point, and keep generated overload metadata using the existing LibIDL
types for now.
2026-06-08 01:11:40 +02:00

380 lines
15 KiB
Python

# Copyright (c) 2026-present, the Ladybird developers.
#
# SPDX-License-Identifier: BSD-2-Clause
from dataclasses import dataclass
from dataclasses import field
from dataclasses import fields
from dataclasses import is_dataclass
from dataclasses import replace
from typing import Any
from typing import Callable
from typing import Iterable
from typing import Optional
from typing import Protocol
from typing import TypeVar
from typing import Union
from typing import cast
from Utils.webidl_parser import CallbackFunction
from Utils.webidl_parser import Dictionary
from Utils.webidl_parser import Enumeration
from Utils.webidl_parser import IDLParameterizedType
from Utils.webidl_parser import IDLType
from Utils.webidl_parser import IDLUnionType
from Utils.webidl_parser import IncludedMixin
from Utils.webidl_parser import Interface
from Utils.webidl_parser import Module
from Utils.webidl_parser import Typedef
class NamedDefinition(Protocol):
name: str
Definition = TypeVar("Definition", bound=NamedDefinition)
def collect_definitions_by_name(
modules: list[Module], definitions_for_module: Callable[[Module], Iterable[Definition]]
) -> dict[str, Definition]:
return {definition.name: definition for module in modules for definition in definitions_for_module(module)}
def collect_partial_definitions_by_name(
modules: list[Module], definitions_for_module: Callable[[Module], Iterable[Definition]]
) -> dict[str, list[Definition]]:
partial_definitions: dict[str, list[Definition]] = {}
for module in modules:
for definition in definitions_for_module(module):
partial_definitions.setdefault(definition.name, []).append(definition)
return partial_definitions
def merge_partial_definitions_by_name(
modules: list[Module],
definitions_for_module: Callable[[Module], Iterable[Definition]],
partial_definitions_for_module: Callable[[Module], Iterable[Definition]],
merge: Callable[[Definition, list[Definition]], Definition],
) -> dict[str, Definition]:
definitions = collect_definitions_by_name(modules, definitions_for_module)
partial_definitions = collect_partial_definitions_by_name(modules, partial_definitions_for_module)
return {type_: merge(definition, partial_definitions.get(type_, [])) for type_, definition in definitions.items()}
@dataclass
class GenerationContext:
modules: list[Module]
callback_functions: dict[str, CallbackFunction] = field(init=False)
dictionaries: dict[str, Dictionary] = field(init=False)
enumerations: dict[str, Enumeration] = field(init=False)
interfaces: dict[str, Interface] = field(init=False)
typedefs: dict[str, Typedef] = field(init=False)
mixins: dict[str, Interface] = field(init=False)
def __post_init__(self) -> None:
self.callback_functions = collect_definitions_by_name(self.modules, lambda module: module.callback_functions)
self.enumerations = collect_definitions_by_name(self.modules, lambda module: module.enumerations)
self.typedefs = collect_definitions_by_name(self.modules, lambda module: module.typedefs)
self.resolve_dictionaries()
self.resolve_mixins()
self.resolve_interfaces()
self.resolve_typedefs()
self.resolve_modules()
def resolve_dictionaries(self) -> None:
self.dictionaries = merge_partial_definitions_by_name(
self.modules,
lambda module: module.dictionaries,
lambda module: module.partial_dictionaries,
merge_dictionary,
)
def resolve_mixins(self) -> None:
self.mixins = merge_partial_definitions_by_name(
self.modules,
lambda module: module.mixins,
lambda module: module.partial_mixins,
merge_mixin,
)
def resolve_interfaces(self) -> None:
interfaces = {
module.interface.name: module.interface for module in self.modules if module.interface is not None
}
partial_interfaces = collect_partial_definitions_by_name(self.modules, lambda module: module.partial_interfaces)
included_mixins: dict[str, list[IncludedMixin]] = {}
seen_included_mixins: set[tuple[str, str]] = set()
for module in self.modules:
for included_mixin in module.included_mixins:
key = (
included_mixin.interface_name,
included_mixin.mixin_name,
)
if key in seen_included_mixins:
continue
seen_included_mixins.add(key)
included_mixins.setdefault(key[0], []).append(included_mixin)
self.interfaces = {
type_: merge_interface(
interface,
partial_interfaces.get(type_, []),
included_mixins.get(type_, []),
self.mixins,
)
for type_, interface in interfaces.items()
}
def resolve_modules(self) -> None:
self.modules = [self.resolve_module(module) for module in self.modules]
def resolve_module(self, module: Module) -> Module:
return replace(
module,
interface=self.interfaces[module.interface.name] if module.interface is not None else None,
dictionaries=[self.dictionaries[dictionary.name] for dictionary in module.dictionaries],
mixins=[self.mixins[mixin.name] for mixin in module.mixins],
partial_interfaces=[],
partial_dictionaries=[],
partial_mixins=[],
included_mixins=[],
)
def resolve_typedefs(self) -> None:
self.typedefs = {
type_: replace(typedef, type=self.resolve_typedef(typedef.type)) for type_, typedef in self.typedefs.items()
}
self.callback_functions = self.resolve_typedefs_in_mapping(self.callback_functions)
self.dictionaries = self.resolve_typedefs_in_mapping(self.dictionaries)
self.mixins = self.resolve_typedefs_in_mapping(self.mixins)
self.interfaces = self.resolve_typedefs_in_mapping(self.interfaces)
def resolve_typedefs_in_mapping(self, definitions):
return {type_: self.resolve_typedefs_in(definition) for type_, definition in definitions.items()}
def resolve_typedefs_in(self, value: Any) -> Any:
if isinstance(value, IDLType):
return self.resolve_typedef(value)
if isinstance(value, list):
return [self.resolve_typedefs_in(item) for item in value]
if isinstance(value, tuple):
return tuple(self.resolve_typedefs_in(item) for item in value)
if is_dataclass(value):
return replace(
cast(Any, value),
**{
field.name: self.resolve_typedefs_in(getattr(value, field.name))
for field in fields(value)
if field.init
},
)
return value
def callback_function(self, type_: IDLType) -> Optional[CallbackFunction]:
return self.callback_functions.get(type_.name)
def dictionary(self, type_: IDLType) -> Optional[Dictionary]:
return self.dictionaries.get(type_.name)
def dictionary_type_names(self, *types: IDLType) -> set[str]:
return {
nested_type.name
for type_ in types
for nested_type in type_.nested_types()
if self.dictionary(nested_type) is not None
}
def dictionary_parent(self, dictionary: Dictionary) -> Optional[Dictionary]:
if not dictionary.parent_name:
return None
parent_dictionary = self.dictionaries.get(dictionary.parent_name)
if parent_dictionary is None:
raise RuntimeError(
f"Dictionary '{dictionary.name}' inherits from unknown dictionary '{dictionary.parent_name}'"
)
return parent_dictionary
def dictionary_inheritance_stack(self, dictionary: Dictionary) -> list[Dictionary]:
stack = [dictionary]
while stack[-1].parent_name:
parent = self.dictionary_parent(stack[-1])
if parent is None:
break
stack.append(parent)
return stack
# https://webidl.spec.whatwg.org/#create-an-inheritance-stack
def inheritance_stack(self, interface: Interface) -> list[Interface]:
# 1. Let stack be a new stack.
# 2. Push I onto stack.
stack = [interface]
# 3. While I inherits from an interface,
# 1. Let I be that interface.
# 2. Push I onto stack.
while stack[-1].parent_name:
parent = self.interfaces.get(stack[-1].parent_name)
if parent is None:
break
stack.append(parent)
# 4. Return stack.
return stack
def enumeration(self, type_: IDLType) -> Optional[Enumeration]:
return self.enumerations.get(type_.name)
def interface(self, type_: IDLType) -> Optional[Interface]:
return self.interfaces.get(type_.name)
def resolve_typedef(self, type_: IDLType) -> IDLType:
resolved_type = type_
if isinstance(resolved_type, IDLUnionType):
return IDLUnionType(
[self.resolve_typedef(member_type) for member_type in resolved_type.member_types],
resolved_type.nullable,
)
if isinstance(resolved_type, IDLParameterizedType):
return IDLParameterizedType(
resolved_type.name,
[self.resolve_typedef(parameter) for parameter in resolved_type.parameters],
resolved_type.nullable,
)
seen_types: set[IDLType] = set()
while resolved_type.name in self.typedefs:
resolved_type_without_nullable = resolved_type.without_nullable()
if resolved_type_without_nullable in seen_types:
raise RuntimeError(f"Typedef '{resolved_type.name}' resolves recursively")
seen_types.add(resolved_type_without_nullable)
typedef_type = self.typedefs[resolved_type.name].type
resolved_type = typedef_type.clone_with_nullable(resolved_type.nullable or typedef_type.nullable)
if isinstance(resolved_type, IDLUnionType):
return IDLUnionType(
[self.resolve_typedef(member_type) for member_type in resolved_type.member_types],
resolved_type.nullable,
)
if isinstance(resolved_type, IDLParameterizedType):
return IDLParameterizedType(
resolved_type.name,
[self.resolve_typedef(parameter) for parameter in resolved_type.parameters],
resolved_type.nullable,
)
return resolved_type
def merge_interface_members(target: Interface, source: Interface) -> None:
target.constants.extend(source.constants)
regular_attributes = merge_definition_extended_attributes(source, source.regular_attributes)
target.regular_attributes.extend(regular_attributes)
target.static_attributes.extend(merge_definition_extended_attributes(source, source.static_attributes))
target.regular_operations.extend(merge_definition_extended_attributes(source, source.regular_operations))
target.static_operations.extend(merge_definition_extended_attributes(source, source.static_operations))
target.constructors.extend(merge_definition_extended_attributes(source, source.constructors))
if target.stringifier is None and source.stringifier is not None:
if source.stringifier.attribute is None:
target.stringifier = source.stringifier
else:
attribute_index = source.regular_attributes.index(source.stringifier.attribute)
target.stringifier = replace(
source.stringifier,
extended_attributes=regular_attributes[attribute_index].extended_attributes,
attribute=regular_attributes[attribute_index],
)
target.named_property_getter = target.named_property_getter or source.named_property_getter
target.indexed_property_getter = target.indexed_property_getter or source.indexed_property_getter
target.named_property_setter = target.named_property_setter or source.named_property_setter
target.named_property_deleter = target.named_property_deleter or source.named_property_deleter
target.indexed_property_setter = target.indexed_property_setter or source.indexed_property_setter
target.maplike = target.maplike or source.maplike
target.setlike = target.setlike or source.setlike
target.iterable = target.iterable or source.iterable
def merge_interface(
interface: Interface,
partial_interfaces: list[Interface],
included_mixins: list[IncludedMixin],
mixins: dict[str, Interface],
) -> Interface:
if not partial_interfaces and not included_mixins:
return interface
merged_interface = copy_interface(interface)
for partial_interface in partial_interfaces:
if partial_interface.extended_attributes.get("Exposed") == "Nobody":
continue
merge_interface_members(merged_interface, partial_interface)
for included_mixin in included_mixins:
mixin = mixins.get(included_mixin.mixin_name)
if mixin is None:
raise RuntimeError(f"Included mixin '{included_mixin.mixin_name}' does not exist")
merge_interface_members(merged_interface, mixin)
return merged_interface
def merge_mixin(mixin: Interface, partial_mixins: list[Interface]) -> Interface:
if not partial_mixins:
return mixin
merged_mixin = copy_interface(mixin)
for partial_mixin in partial_mixins:
if partial_mixin.extended_attributes.get("Exposed") == "Nobody":
continue
merge_interface_members(merged_mixin, partial_mixin)
return merged_mixin
def copy_interface(interface: Interface) -> Interface:
return replace(
interface,
constants=list(interface.constants),
regular_attributes=list(interface.regular_attributes),
static_attributes=list(interface.static_attributes),
regular_operations=list(interface.regular_operations),
static_operations=list(interface.static_operations),
constructors=list(interface.constructors),
)
def merge_dictionary(dictionary: Dictionary, partial_dictionaries: list[Dictionary]) -> Dictionary:
if not partial_dictionaries:
return dictionary
merged_dictionary = replace(
dictionary,
members=list(dictionary.members),
extended_attributes=dict(dictionary.extended_attributes),
)
for partial_dictionary in partial_dictionaries:
merged_dictionary.members.extend(
merge_definition_extended_attributes(partial_dictionary, partial_dictionary.members)
)
merged_dictionary.members.sort(key=lambda member: member.name)
return merged_dictionary
def merge_definition_extended_attributes(source: Union[Interface, Dictionary], members):
if not source.extended_attributes:
return members
return [
replace(member, extended_attributes={**source.extended_attributes, **member.extended_attributes})
for member in members
]