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.
380 lines
15 KiB
Python
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
|
|
]
|