chromium/third_party/blink/renderer/bindings/scripts/bind_gen/codegen_context.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.

import copy

import web_idl

from .codegen_format import NonRenderable


class CodeGenContext(object):
    """
    Represents a context of code generation.

    Note that this is not Mako template context itself, however
    CodeGenContext's attributes will be bound as Mako's global template
    variables as below.

      code_node.set_base_template_vars(cg_context.template_bindings())

    Then, |CodeGenContext.interface| will be available in template text as
    "${interface}".  So, an instance of CodeGenContext represents a set of
    global template variables.

    CodeGenContext is immutable.  A new state should be created via
    |make_copy|.

      new_cg_context = old_cg_context.make_copy(
          var1=new_value1, var2=new_value2, ...)

    The immutability is important because CodeNodes may be created lazily from
    an instance of CodeGenContext.  For example,

      def foo(cg_context):
        def define_symbol(symbol_node):
          node = SymbolDefinitionNode(symbol_node)
          node.append(TextNode("{}".format(cg_context.class_name)))
          return node
        symbol = SymbolNode("sym", definition_constructor=define_symbol)
        ...

    in this case, |define_symbol| may be run after the execution of |foo|
    completes.  |define_symbol| is a closure which captures |cg_context|.
    So, it's important that CodeGenContext is immutable in order to avoid any
    surprising side effect.
    """

    # "for_world" attribute values
    MAIN_WORLD = "main"
    NON_MAIN_WORLDS = "other"
    ALL_WORLDS = "all"

    # "v8_callback_type" attribute values
    #
    # void (*)(const v8::FunctionCallbackInfo<v8::Value>&)
    V8_FUNCTION_CALLBACK = "v8::FunctionCallback"
    # void (*)(v8::Local<v8::Name>,
    #          const v8::PropertyCallbackInfo<v8::Value>&)
    V8_ACCESSOR_NAME_GETTER_CALLBACK = "v8::AccessorNameGetterCallback"
    # void (*)(v8::Local<v8::Name>, v8::Local<v8::Value>,
    #          const v8::PropertyCallbackInfo<void>&)
    V8_ACCESSOR_NAME_SETTER_CALLBACK = "v8::AccessorNameSetterCallback"
    # v8::Intercepted (*)(v8::Local<v8::Name>,
    #                     const v8::PropertyCallbackInfo<v8::Value>&)
    V8_NAMED_PROPERTY_GETTER_CALLBACK = "v8::NamedPropertyGetterCallback"
    # v8::Intercepted (*)(v8::Local<v8::Name>, v8::Local<v8::Value>,
    #                     const v8::PropertyCallbackInfo<void>&)
    V8_NAMED_PROPERTY_SETTER_CALLBACK = "v8::NamedPropertySetterCallback"
    # Others
    V8_OTHER_CALLBACK = "other callback type"

    @classmethod
    def init(cls):
        """Initialize the class.  Must be called exactly once."""
        assert not hasattr(cls, "_was_initialized"), "Do not call twice."
        cls._was_initialized = True

        # List of
        #   attribute name: default value
        cls._context_attrs = {
            # Top-level definition
            "async_iterator": None,
            "callback_function": None,
            "callback_interface": None,
            "dictionary": None,
            "enumeration": None,
            "interface": None,
            "namespace": None,
            "observable_array": None,
            "sync_iterator": None,
            "typedef": None,
            "union": None,

            # Class-member-ish definition
            "attribute": None,
            "attribute_get": False,
            "attribute_set": False,
            "constant": None,
            "constructor": None,
            "constructor_group": None,
            "dict_member": None,
            "exposed_construct": None,
            "is_legacy_factory_function": False,
            "legacy_window_alias": None,
            "operation": None,
            "operation_group": None,

            # Special member-ish definition
            "indexed_interceptor_kind": None,
            "indexed_property_getter": None,
            "indexed_property_setter": None,
            "named_interceptor_kind": None,
            "named_property_getter": None,
            "named_property_setter": None,
            "named_property_deleter": None,
            "stringifier": None,

            # Cache of a tuple of dictionary._DictionaryMember for the own
            # members of the dictionary of which the Blink class is being
            # generated.  The cache is used in dictionary.py to save code
            # generation time.
            "dictionary_own_members": (),
            # Cache of a tuple of union._UnionMember for the flattened member
            # types of the union of which the Blink class is being generated.
            # The cache is used in union.py to save code generation time.
            "union_members": (),

            # The names of the class being generated and its base class.
            "base_class_name": None,
            "class_name": None,

            # Main world or all worlds
            # Used via [PerWorldBindings] to optimize the code path of the main
            # world.
            "for_world": cls.ALL_WORLDS,

            # True when generating a callback of [NoAllocDirectCall].
            "no_alloc_direct_call": False,

            # Type of V8 callback function which implements IDL attribute,
            # IDL operation, etc.
            "v8_callback_type": cls.V8_FUNCTION_CALLBACK,
        }

        # List of computational attribute names
        cls._computational_attrs = (
            "class_like",
            "function_like",
            "idl_definition",
            "idl_location",
            "idl_location_and_name",
            "idl_name",
            "may_throw_exception",
            "member_like",
            "property_",
            "return_type",
        )

        # Define public readonly properties of this class.
        for attr in cls._context_attrs.keys():

            def make_get():
                _attr = cls._internal_attr(attr)

                def get(self):
                    return getattr(self, _attr)

                return get

            setattr(cls, attr, property(make_get()))

    @staticmethod
    def _internal_attr(attr):
        return "_{}".format(attr)

    def __init__(self, **kwargs):
        assert CodeGenContext._was_initialized

        for arg in kwargs.keys():
            assert arg in self._context_attrs, "Unknown argument: {}".format(
                arg)

        for attr, default_value in self._context_attrs.items():
            value = kwargs[attr] if attr in kwargs else default_value
            assert (default_value is None
                    or type(value) is type(default_value)), (
                        "Type mismatch at argument: {}".format(attr))
            setattr(self, self._internal_attr(attr), value)

    def make_copy(self, **kwargs):
        """
        Returns a copy of this context applying the updates given as the
        arguments.
        """
        for arg in kwargs.keys():
            assert arg in self._context_attrs, "Unknown argument: {}".format(
                arg)

        new_object = copy.copy(self)

        for attr, new_value in kwargs.items():
            old_value = getattr(self, attr)
            assert old_value is None or type(new_value) is type(old_value), (
                "Type mismatch at argument: {}".format(attr))
            setattr(new_object, self._internal_attr(attr), new_value)

        return new_object

    def template_bindings(self):
        """
        Returns a bindings object to be passed into
        |CodeNode.add_template_vars|.  Only properties with a non-None value are
        bound so that it's easy to detect invalid use cases (use of an unbound
        template variable raises a NameError).
        """
        bindings = {}

        for attr in self._context_attrs.keys():
            value = getattr(self, attr)
            if value is None:
                value = NonRenderable(attr)
            bindings[attr] = value

        for attr in self._computational_attrs:
            value = getattr(self, attr)
            if value is None:
                value = NonRenderable(attr)
            bindings[attr.strip("_")] = value

        return bindings

    @property
    def class_like(self):
        return (self.async_iterator or self.callback_interface
                or self.dictionary or self.interface or self.namespace
                or self.sync_iterator)

    @property
    def does_override_idl_return_type(self):
        # Blink implementation returns in a type different from the IDL type.
        # Namely, IndexedPropertySetterResult, NamedPropertySetterResult, and
        # NamedPropertyDeleterResult are returned ignoring the operation's
        # return type.
        return (self.indexed_property_setter or self.named_property_setter
                or self.named_property_deleter)

    @property
    def function_like(self):
        return (self.callback_function or self.constructor or self.operation
                or self._indexed_or_named_property)

    @property
    def idl_definition(self):
        return (self.callback_function or self.callback_interface
                or self.dictionary or self.enumeration or self.interface
                or self.namespace or self.typedef or self.union)

    @property
    def idl_location(self):
        idl_def = self.member_like or self.idl_definition
        if idl_def and not isinstance(idl_def, web_idl.Union):
            location = idl_def.debug_info.location
            text = location.filepath
            if location.line_number is not None:
                text += ":{}".format(location.line_number)
            return text
        return "<<unknown path>>"

    @property
    def idl_location_and_name(self):
        return "{}: {}".format(self.idl_location, self.idl_name)

    @property
    def idl_name(self):
        member = self.member_like or self.property_
        if member:
            return "{}.{}".format(self.class_like.identifier,
                                  member.identifier)
        if self.idl_definition:
            return self.idl_definition.identifier
        return "<<unknown name>>"

    @property
    def is_return_type_promise_type(self):
        if self.attribute:
            return self.attribute.idl_type.unwrap().is_promise
        if self.operation_group:
            return self.operation_group[0].return_type.unwrap().is_promise
        return False

    @property
    def is_interceptor_returning_v8intercepted(self):
        return bool((self.indexed_interceptor_kind
                     and self.indexed_interceptor_kind != "Enumerator")
                    or (self.named_interceptor_kind
                        and self.named_interceptor_kind != "Enumerator")
                    or (self.v8_callback_type
                        == CodeGenContext.V8_NAMED_PROPERTY_GETTER_CALLBACK)
                    or (self.v8_callback_type
                        == CodeGenContext.V8_NAMED_PROPERTY_SETTER_CALLBACK))

    @property
    def logging_target(self):
        return (self.attribute or self.constant or self.constructor
                or self.constructor_group or self.dict_member
                or (self.legacy_window_alias or self.exposed_construct)
                or self.operation or self.operation_group
                or (self.stringifier and self.stringifier.operation)
                or self._indexed_or_named_property)

    @property
    def may_throw_exception(self):
        if not self.member_like:
            return False
        ext_attr = self.member_like.extended_attributes.get("RaisesException")
        if not ext_attr:
            return False
        return (not ext_attr.values
                or (self.attribute_get and "Getter" in ext_attr.values)
                or (self.attribute_set and "Setter" in ext_attr.values))

    @property
    def member_like(self):
        return (self.attribute or self.constant or self.constructor
                or self.dict_member or self.operation
                or self._indexed_or_named_property)

    @property
    def property_(self):
        return (self.attribute or self.constant or self.constructor_group
                or self.dict_member
                or (self.legacy_window_alias or self.exposed_construct)
                or self.operation_group
                or (self.stringifier and self.stringifier.operation)
                or self._indexed_or_named_property)

    @property
    def return_type(self):
        if self.attribute_get:
            return self.attribute.idl_type
        function_like = self.function_like
        if function_like:
            return function_like.return_type
        return None

    @property
    def _indexed_or_named_property(self):
        return (self.indexed_property_getter or self.indexed_property_setter
                or self.named_property_getter or self.named_property_setter
                or self.named_property_deleter)


CodeGenContext.init()