chromium/third_party/blink/renderer/bindings/scripts/web_idl/union.py

# Copyright 2019 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

from .code_generator_info import CodeGeneratorInfoMutable
from .composition_parts import Identifier
from .composition_parts import WithCodeGeneratorInfo
from .composition_parts import WithComponent
from .composition_parts import WithDebugInfo
from .composition_parts import WithIdentifier


class Union(WithIdentifier, WithCodeGeneratorInfo, WithComponent,
            WithDebugInfo):
    """
    Union class makes a group of union types with the same flattened member
    types and the same result whether it includes a nullable type or not.

    For example, the following union types will be grouped into one Union
    instance.
      (A? or B or C), (A or B? or C), ((A or B) or C?), (A or (B or C?)), ...
    All these unions have the same set of flattened member types (A, B, C) and
    include a nullable type.

    However, all following union types will be grouped into separate Union
    instances.
      (A or B), ([X] A or B), ([Y] A or B)
    IdlType(A), IdlType([X] A), and IdlType([Y] A) are all distinguished from
    each other as they behave differently.  Bindings code generators are
    expected to define an implementation class for each Union instance.
    """

    _null_token = "Null"

    @classmethod
    def unique_token(cls, union_type):
        """
        Returns an unique token per a set of union types that are considered
        as the same group.
        """
        assert union_type.is_union

        token_pieces = []

        def collect_token_pieces(idl_type):
            idl_type = idl_type.unwrap()
            if idl_type.is_union:
                for member_type in idl_type.member_types:
                    collect_token_pieces(member_type)
            else:
                # Typename is not guaranteed to be unique, however it's
                # unlikely that a conflict happens.
                token_pieces.append(
                    idl_type.type_name_with_extended_attribute_key_values)

        collect_token_pieces(union_type)
        token_pieces.sort()
        if union_type.does_include_nullable_type:
            token_pieces.append(cls._null_token)
        return tuple(token_pieces)

    class IR(object):
        # Note that Union.IR is, despite of its name, very different from other
        # IDL definitions' IR classes.  This class is not meant to be stored in
        # IRMap nor managed with 'compilation phase'.

        def __init__(self, token, union_types):
            assert all(union_type.is_union for union_type in union_types)

            self.token = token
            self._member_set = set(token)
            self.union_types = union_types
            self.typedefs = []
            self.sub_union_irs = []
            self.public_object = None

        def __lt__(self, other):
            if len(self.token) == len(other.token):
                return self.token < other.token
            else:
                return len(self.token) < len(other.token)

        def contains(self, other):
            assert isinstance(other, Union.IR)
            return (self.token != other.token
                    and self._member_set.issuperset(other._member_set))

    def __init__(self, ir):
        assert isinstance(ir, Union.IR)
        assert ir.public_object is None

        identifier = Identifier('Union_{}'.format('_'.join(ir.token)))
        union_type = ir.union_types[0]
        flattened_member_types = union_type.flattened_member_types
        does_include_nullable_type = union_type.does_include_nullable_type
        does_include_nullable_or_dict = (
            union_type.does_include_nullable_or_dict)

        typedef_members = set()
        union_members = set()
        for union_type in ir.union_types:
            assert union_type.flattened_member_types == flattened_member_types
            assert (union_type.does_include_nullable_type ==
                    does_include_nullable_type)
            for member_type in union_type.member_types:
                if member_type.is_typedef:
                    typedef_members.add(member_type.typedef_object)
        for sub_union_ir in ir.sub_union_irs:
            assert isinstance(sub_union_ir.public_object, Union)
            typedef_members.update(sub_union_ir.typedefs)
            union_members.add(sub_union_ir.public_object)

        components = set()
        for_testing = [False]

        def collect_primary_component(idl_type):
            type_definition_object = idl_type.type_definition_object
            if type_definition_object and type_definition_object.components:
                components.add(type_definition_object.components[0])
            if (type_definition_object and
                    type_definition_object.code_generator_info.for_testing):
                for_testing[0] = True

        for idl_type in flattened_member_types:
            idl_type.apply_to_all_composing_elements(collect_primary_component)
        code_generator_info = CodeGeneratorInfoMutable()
        code_generator_info.set_for_testing(for_testing[0])

        WithIdentifier.__init__(self, identifier)
        WithCodeGeneratorInfo.__init__(self,
                                       code_generator_info,
                                       readonly=True)
        WithComponent.__init__(self, sorted(components), readonly=True)
        WithDebugInfo.__init__(self)

        sort_key_typename = lambda idl_type: (
            idl_type.type_name_with_extended_attribute_key_values)
        sort_key_identifier = lambda x: x.identifier

        self._idl_types = tuple(ir.union_types)
        self._member_tokens = ir.token
        self._flattened_member_types = tuple(
            sorted(flattened_member_types, key=sort_key_typename))
        self._does_include_nullable_type = does_include_nullable_type
        self._does_include_nullable_or_dict = does_include_nullable_or_dict
        self._typedef_members = tuple(
            sorted(typedef_members, key=sort_key_identifier))
        self._union_members = tuple(
            sorted(union_members, key=sort_key_identifier))
        self._aliasing_typedefs = tuple(
            sorted(ir.typedefs, key=sort_key_identifier))

        ir.public_object = self

    @property
    def idl_types(self):
        """Returns a list of IdlTypes which this object represents."""
        return self._idl_types

    @property
    def member_tokens(self):
        """Returns a list of unique names of union member types."""
        return self._member_tokens

    @property
    def flattened_member_types(self):
        """
        Returns the same list of flattened member types as
        IdlType.flattened_member_types.
        """
        return self._flattened_member_types

    @property
    def does_include_nullable_type(self):
        """
        Returns True if any of member type is nullable or a member union
        includes a nullable type.
        """
        return self._does_include_nullable_type

    @property
    def does_include_nullable_or_dict(self):
        """
        Returns True if this type includes a nullable type or a dictionary
        type.
        """
        return self._does_include_nullable_or_dict

    @property
    def typedef_members(self):
        """
        Returns a list of typedef types which are direct members of union types
        which this object represents.

        Given the following union types,
          (AT or B), (A or BT) where typedef A AT, and typedef B BT
        typedef_members returns a list of IdlType(AT) and IdlType(BT).
        """
        return self._typedef_members

    @property
    def union_members(self):
        """
        Returns a list of union types which are direct members of union types
        which this object represents.

        Given the following union types,
          ((A or B) or C), (A or (B or C))
        union_members returns a list of IdlType(A or B) and IdlType(B or C).
        """
        return self._union_members

    @property
    def aliasing_typedefs(self):
        """
        Returns a list of typedef types which are aliases to union types which
        this object represents.

        Given the following typedef definitions,
          typedef ((A or B) or C) T1;
          typedef (A or (B or C)) T2;
        aliasing_typedefs returns a list of IdlType(T1) and IdlType(T2).
        """
        return self._aliasing_typedefs