• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright (C) 2013 Google Inc. All rights reserved.
2# coding=utf-8
3#
4# Redistribution and use in source and binary forms, with or without
5# modification, are permitted provided that the following conditions are
6# met:
7#
8#     * Redistributions of source code must retain the above copyright
9# notice, this list of conditions and the following disclaimer.
10#     * Redistributions in binary form must reproduce the above
11# copyright notice, this list of conditions and the following disclaimer
12# in the documentation and/or other materials provided with the
13# distribution.
14#     * Neither the name of Google Inc. nor the names of its
15# contributors may be used to endorse or promote products derived from
16# this software without specific prior written permission.
17#
18# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30"""Generate template values for an interface.
31
32Design doc: http://www.chromium.org/developers/design-documents/idl-compiler
33"""
34
35from collections import defaultdict
36import itertools
37from operator import itemgetter
38
39import idl_definitions
40from idl_definitions import IdlOperation
41import idl_types
42from idl_types import IdlType, inherits_interface
43import v8_attributes
44from v8_globals import includes
45import v8_methods
46import v8_types
47from v8_types import cpp_ptr_type, cpp_template_type
48import v8_utilities
49from v8_utilities import (capitalize, conditional_string, cpp_name, gc_type,
50                          has_extended_attribute_value, runtime_enabled_function_name,
51                          extended_attribute_value_as_list)
52
53
54INTERFACE_H_INCLUDES = frozenset([
55    'bindings/core/v8/ScriptWrappable.h',
56    'bindings/core/v8/V8Binding.h',
57    'bindings/core/v8/V8DOMWrapper.h',
58    'bindings/core/v8/WrapperTypeInfo.h',
59    'platform/heap/Handle.h',
60])
61INTERFACE_CPP_INCLUDES = frozenset([
62    'bindings/core/v8/ExceptionState.h',
63    'bindings/core/v8/V8DOMConfiguration.h',
64    'bindings/core/v8/V8HiddenValue.h',
65    'bindings/core/v8/V8ObjectConstructor.h',
66    'core/dom/ContextFeatures.h',
67    'core/dom/Document.h',
68    'platform/RuntimeEnabledFeatures.h',
69    'platform/TraceEvent.h',
70    'wtf/GetPtr.h',
71    'wtf/RefPtr.h',
72])
73
74
75def interface_context(interface):
76    includes.clear()
77    includes.update(INTERFACE_CPP_INCLUDES)
78    header_includes = set(INTERFACE_H_INCLUDES)
79
80    parent_interface = interface.parent
81    if parent_interface:
82        header_includes.update(v8_types.includes_for_interface(parent_interface))
83    extended_attributes = interface.extended_attributes
84
85    is_audio_buffer = inherits_interface(interface.name, 'AudioBuffer')
86    if is_audio_buffer:
87        includes.add('modules/webaudio/AudioBuffer.h')
88
89    is_document = inherits_interface(interface.name, 'Document')
90    if is_document:
91        includes.update(['bindings/core/v8/ScriptController.h',
92                         'bindings/core/v8/WindowProxy.h',
93                         'core/frame/LocalFrame.h'])
94
95    # [ActiveDOMObject]
96    is_active_dom_object = 'ActiveDOMObject' in extended_attributes
97
98    # [CheckSecurity]
99    is_check_security = 'CheckSecurity' in extended_attributes
100    if is_check_security:
101        includes.add('bindings/core/v8/BindingSecurity.h')
102
103    # [DependentLifetime]
104    is_dependent_lifetime = 'DependentLifetime' in extended_attributes
105
106    # [Iterable]
107    iterator_method = None
108    if 'Iterable' in extended_attributes:
109        iterator_operation = IdlOperation(interface.idl_name)
110        iterator_operation.name = 'iterator'
111        iterator_operation.idl_type = IdlType('Iterator')
112        iterator_operation.extended_attributes['RaisesException'] = None
113        iterator_operation.extended_attributes['CallWith'] = 'ScriptState'
114        iterator_method = v8_methods.method_context(interface,
115                                                    iterator_operation)
116
117    # [MeasureAs]
118    is_measure_as = 'MeasureAs' in extended_attributes
119    if is_measure_as:
120        includes.add('core/frame/UseCounter.h')
121
122    # [SetWrapperReferenceFrom]
123    reachable_node_function = extended_attributes.get('SetWrapperReferenceFrom')
124    if reachable_node_function:
125        includes.update(['bindings/core/v8/V8GCController.h',
126                         'core/dom/Element.h'])
127
128    # [SetWrapperReferenceTo]
129    set_wrapper_reference_to_list = [{
130        'name': argument.name,
131        # FIXME: properly should be:
132        # 'cpp_type': argument.idl_type.cpp_type_args(raw_type=True),
133        # (if type is non-wrapper type like NodeFilter, normally RefPtr)
134        # Raw pointers faster though, and NodeFilter hacky anyway.
135        'cpp_type': argument.idl_type.implemented_as + '*',
136        'idl_type': argument.idl_type,
137        'v8_type': v8_types.v8_type(argument.idl_type.name),
138    } for argument in extended_attributes.get('SetWrapperReferenceTo', [])]
139    for set_wrapper_reference_to in set_wrapper_reference_to_list:
140        set_wrapper_reference_to['idl_type'].add_includes_for_type()
141
142    # [NotScriptWrappable]
143    is_script_wrappable = 'NotScriptWrappable' not in extended_attributes
144
145    # [SpecialWrapFor]
146    if 'SpecialWrapFor' in extended_attributes:
147        special_wrap_for = extended_attribute_value_as_list(interface, 'SpecialWrapFor')
148    else:
149        special_wrap_for = []
150    for special_wrap_interface in special_wrap_for:
151        v8_types.add_includes_for_interface(special_wrap_interface)
152
153    # [Custom=Wrap], [SetWrapperReferenceFrom]
154    has_visit_dom_wrapper = (
155        has_extended_attribute_value(interface, 'Custom', 'VisitDOMWrapper') or
156        reachable_node_function or
157        set_wrapper_reference_to_list)
158
159    this_gc_type = gc_type(interface)
160
161    wrapper_class_id = ('NodeClassId' if inherits_interface(interface.name, 'Node') else 'ObjectClassId')
162
163    context = {
164        'conditional_string': conditional_string(interface),  # [Conditional]
165        'cpp_class': cpp_name(interface),
166        'gc_type': this_gc_type,
167        # FIXME: Remove 'EventTarget' special handling, http://crbug.com/383699
168        'has_access_check_callbacks': (is_check_security and
169                                       interface.name != 'Window' and
170                                       interface.name != 'EventTarget'),
171        'has_custom_legacy_call_as_function': has_extended_attribute_value(interface, 'Custom', 'LegacyCallAsFunction'),  # [Custom=LegacyCallAsFunction]
172        'has_custom_to_v8': has_extended_attribute_value(interface, 'Custom', 'ToV8'),  # [Custom=ToV8]
173        'has_custom_wrap': has_extended_attribute_value(interface, 'Custom', 'Wrap'),  # [Custom=Wrap]
174        'has_visit_dom_wrapper': has_visit_dom_wrapper,
175        'header_includes': header_includes,
176        'interface_name': interface.name,
177        'is_active_dom_object': is_active_dom_object,
178        'is_audio_buffer': is_audio_buffer,
179        'is_check_security': is_check_security,
180        'is_dependent_lifetime': is_dependent_lifetime,
181        'is_document': is_document,
182        'is_event_target': inherits_interface(interface.name, 'EventTarget'),
183        'is_exception': interface.is_exception,
184        'is_node': inherits_interface(interface.name, 'Node'),
185        'is_script_wrappable': is_script_wrappable,
186        'iterator_method': iterator_method,
187        'lifetime': 'Dependent'
188            if (has_visit_dom_wrapper or
189                is_active_dom_object or
190                is_dependent_lifetime)
191            else 'Independent',
192        'measure_as': v8_utilities.measure_as(interface),  # [MeasureAs]
193        'parent_interface': parent_interface,
194        'pass_cpp_type': cpp_template_type(
195            cpp_ptr_type('PassRefPtr', 'RawPtr', this_gc_type),
196            cpp_name(interface)),
197        'reachable_node_function': reachable_node_function,
198        'runtime_enabled_function': runtime_enabled_function_name(interface),  # [RuntimeEnabled]
199        'set_wrapper_reference_to_list': set_wrapper_reference_to_list,
200        'special_wrap_for': special_wrap_for,
201        'v8_class': v8_utilities.v8_class_name(interface),
202        'wrapper_class_id': wrapper_class_id,
203    }
204
205    # Constructors
206    constructors = [constructor_context(interface, constructor)
207                    for constructor in interface.constructors
208                    # FIXME: shouldn't put named constructors with constructors
209                    # (currently needed for Perl compatibility)
210                    # Handle named constructors separately
211                    if constructor.name == 'Constructor']
212    if len(constructors) > 1:
213        context['constructor_overloads'] = overloads_context(constructors)
214
215    # [CustomConstructor]
216    custom_constructors = [{  # Only needed for computing interface length
217        'number_of_required_arguments':
218            number_of_required_arguments(constructor),
219    } for constructor in interface.custom_constructors]
220
221    # [EventConstructor]
222    has_event_constructor = 'EventConstructor' in extended_attributes
223    any_type_attributes = [attribute for attribute in interface.attributes
224                           if attribute.idl_type.name == 'Any']
225    if has_event_constructor:
226        includes.add('bindings/core/v8/Dictionary.h')
227        if any_type_attributes:
228            includes.add('bindings/core/v8/SerializedScriptValue.h')
229
230    # [NamedConstructor]
231    named_constructor = named_constructor_context(interface)
232
233    if (constructors or custom_constructors or has_event_constructor or
234        named_constructor):
235        includes.add('bindings/core/v8/V8ObjectConstructor.h')
236        includes.add('core/frame/LocalDOMWindow.h')
237
238    context.update({
239        'any_type_attributes': any_type_attributes,
240        'constructors': constructors,
241        'has_custom_constructor': bool(custom_constructors),
242        'has_event_constructor': has_event_constructor,
243        'interface_length':
244            interface_length(interface, constructors + custom_constructors),
245        'is_constructor_raises_exception': extended_attributes.get('RaisesException') == 'Constructor',  # [RaisesException=Constructor]
246        'named_constructor': named_constructor,
247    })
248
249    constants = [constant_context(constant) for constant in interface.constants]
250
251    special_getter_constants = []
252    runtime_enabled_constants = []
253    constant_configuration_constants = []
254
255    for constant in constants:
256        if constant['measure_as'] or constant['deprecate_as']:
257            special_getter_constants.append(constant)
258            continue
259        if constant['runtime_enabled_function']:
260            runtime_enabled_constants.append(constant)
261            continue
262        constant_configuration_constants.append(constant)
263
264    # Constants
265    context.update({
266        'constant_configuration_constants': constant_configuration_constants,
267        'constants': constants,
268        'do_not_check_constants': 'DoNotCheckConstants' in extended_attributes,
269        'has_constant_configuration': any(
270            not constant['runtime_enabled_function']
271            for constant in constants),
272        'runtime_enabled_constants': runtime_enabled_constants,
273        'special_getter_constants': special_getter_constants,
274    })
275
276    # Attributes
277    attributes = [v8_attributes.attribute_context(interface, attribute)
278                  for attribute in interface.attributes]
279    context.update({
280        'attributes': attributes,
281        'has_accessors': any(attribute['is_expose_js_accessors'] and attribute['should_be_exposed_to_script'] for attribute in attributes),
282        'has_attribute_configuration': any(
283             not (attribute['is_expose_js_accessors'] or
284                  attribute['is_static'] or
285                  attribute['runtime_enabled_function'] or
286                  attribute['per_context_enabled_function'])
287             and attribute['should_be_exposed_to_script']
288             for attribute in attributes),
289        'has_conditional_attributes': any(attribute['per_context_enabled_function'] or attribute['exposed_test'] for attribute in attributes),
290        'has_constructor_attributes': any(attribute['constructor_type'] for attribute in attributes),
291        'has_replaceable_attributes': any(attribute['is_replaceable'] for attribute in attributes),
292    })
293
294    # Methods
295    methods = [v8_methods.method_context(interface, method)
296               for method in interface.operations
297               if method.name]  # Skip anonymous special operations (methods)
298    compute_method_overloads_context(methods)
299
300    # Stringifier
301    if interface.stringifier:
302        stringifier = interface.stringifier
303        method = IdlOperation(interface.idl_name)
304        method.name = 'toString'
305        method.idl_type = IdlType('DOMString')
306        method.extended_attributes.update(stringifier.extended_attributes)
307        if stringifier.attribute:
308            method.extended_attributes['ImplementedAs'] = stringifier.attribute.name
309        elif stringifier.operation:
310            method.extended_attributes['ImplementedAs'] = stringifier.operation.name
311        methods.append(v8_methods.method_context(interface, method))
312
313    conditionally_enabled_methods = []
314    custom_registration_methods = []
315    method_configuration_methods = []
316
317    for method in methods:
318        # Skip all but one method in each set of overloaded methods.
319        if 'overload_index' in method and 'overloads' not in method:
320            continue
321
322        if 'overloads' in method:
323            overloads = method['overloads']
324            per_context_enabled_function = overloads['per_context_enabled_function_all']
325            conditionally_exposed_function = overloads['exposed_test_all']
326            runtime_enabled_function = overloads['runtime_enabled_function_all']
327            has_custom_registration = overloads['has_custom_registration_all']
328        else:
329            per_context_enabled_function = method['per_context_enabled_function']
330            conditionally_exposed_function = method['exposed_test']
331            runtime_enabled_function = method['runtime_enabled_function']
332            has_custom_registration = method['has_custom_registration']
333
334        if per_context_enabled_function or conditionally_exposed_function:
335            conditionally_enabled_methods.append(method)
336            continue
337        if runtime_enabled_function or has_custom_registration:
338            custom_registration_methods.append(method)
339            continue
340        if method['should_be_exposed_to_script']:
341            method_configuration_methods.append(method)
342
343    for method in methods:
344        # The value of the Function object’s “length” property is a Number
345        # determined as follows:
346        # 1. Let S be the effective overload set for regular operations (if the
347        # operation is a regular operation) or for static operations (if the
348        # operation is a static operation) with identifier id on interface I and
349        # with argument count 0.
350        # 2. Return the length of the shortest argument list of the entries in S.
351        # FIXME: This calculation doesn't take into account whether runtime
352        # enabled overloads are actually enabled, so length may be incorrect.
353        # E.g., [RuntimeEnabled=Foo] void f(); void f(long x);
354        # should have length 1 if Foo is not enabled, but length 0 if it is.
355        method['length'] = (method['overloads']['minarg'] if 'overloads' in method else
356                            method['number_of_required_arguments'])
357
358    context.update({
359        'conditionally_enabled_methods': conditionally_enabled_methods,
360        'custom_registration_methods': custom_registration_methods,
361        'has_origin_safe_method_setter': any(
362            method['is_check_security_for_frame'] and not method['is_read_only']
363            for method in methods),
364        'has_private_script': any(attribute['is_implemented_in_private_script'] for attribute in attributes) or
365            any(method['is_implemented_in_private_script'] for method in methods),
366        'method_configuration_methods': method_configuration_methods,
367        'methods': methods,
368    })
369
370    context.update({
371        'indexed_property_getter': indexed_property_getter(interface),
372        'indexed_property_setter': indexed_property_setter(interface),
373        'indexed_property_deleter': indexed_property_deleter(interface),
374        'is_override_builtins': 'OverrideBuiltins' in extended_attributes,
375        'named_property_getter': named_property_getter(interface),
376        'named_property_setter': named_property_setter(interface),
377        'named_property_deleter': named_property_deleter(interface),
378    })
379
380    return context
381
382
383# [DeprecateAs], [Reflect], [RuntimeEnabled]
384def constant_context(constant):
385    extended_attributes = constant.extended_attributes
386    return {
387        'cpp_class': extended_attributes.get('PartialInterfaceImplementedAs'),
388        'deprecate_as': v8_utilities.deprecate_as(constant),  # [DeprecateAs]
389        'idl_type': constant.idl_type.name,
390        'measure_as': v8_utilities.measure_as(constant),  # [MeasureAs]
391        'name': constant.name,
392        # FIXME: use 'reflected_name' as correct 'name'
393        'reflected_name': extended_attributes.get('Reflect', constant.name),
394        'runtime_enabled_function': runtime_enabled_function_name(constant),
395        'value': constant.value,
396    }
397
398
399################################################################################
400# Overloads
401################################################################################
402
403def compute_method_overloads_context(methods):
404    # Regular methods
405    compute_method_overloads_context_by_type([method for method in methods
406                                              if not method['is_static']])
407    # Static methods
408    compute_method_overloads_context_by_type([method for method in methods
409                                              if method['is_static']])
410
411
412def compute_method_overloads_context_by_type(methods):
413    """Computes |method.overload*| template values.
414
415    Called separately for static and non-static (regular) methods,
416    as these are overloaded separately.
417    Modifies |method| in place for |method| in |methods|.
418    Doesn't change the |methods| list itself (only the values, i.e. individual
419    methods), so ok to treat these separately.
420    """
421    # Add overload information only to overloaded methods, so template code can
422    # easily verify if a function is overloaded
423    for name, overloads in method_overloads_by_name(methods):
424        # Resolution function is generated after last overloaded function;
425        # package necessary information into |method.overloads| for that method.
426        overloads[-1]['overloads'] = overloads_context(overloads)
427        overloads[-1]['overloads']['name'] = name
428
429
430def method_overloads_by_name(methods):
431    """Returns generator of overloaded methods by name: [name, [method]]"""
432    # Filter to only methods that are actually overloaded
433    method_counts = Counter(method['name'] for method in methods)
434    overloaded_method_names = set(name
435                                  for name, count in method_counts.iteritems()
436                                  if count > 1)
437    overloaded_methods = [method for method in methods
438                          if method['name'] in overloaded_method_names]
439
440    # Group by name (generally will be defined together, but not necessarily)
441    return sort_and_groupby(overloaded_methods, itemgetter('name'))
442
443
444def overloads_context(overloads):
445    """Returns |overloads| template values for a single name.
446
447    Sets |method.overload_index| in place for |method| in |overloads|
448    and returns dict of overall overload template values.
449    """
450    assert len(overloads) > 1  # only apply to overloaded names
451    for index, method in enumerate(overloads, 1):
452        method['overload_index'] = index
453
454    effective_overloads_by_length = effective_overload_set_by_length(overloads)
455    lengths = [length for length, _ in effective_overloads_by_length]
456    name = overloads[0].get('name', '<constructor>')
457
458    # Check and fail if all overloads with the shortest acceptable arguments
459    # list are runtime enabled, since we would otherwise set 'length' on the
460    # function object to an incorrect value when none of those overloads were
461    # actually enabled at runtime. The exception is if all overloads are
462    # controlled by the same runtime enabled feature, in which case there would
463    # be no function object at all if it is not enabled.
464    shortest_overloads = effective_overloads_by_length[0][1]
465    if (all(method.get('runtime_enabled_function')
466            for method, _, _ in shortest_overloads) and
467        not common_value(overloads, 'runtime_enabled_function')):
468        raise ValueError('Function.length of %s depends on runtime enabled features' % name)
469
470    # Check and fail if overloads disagree on any of the extended attributes
471    # that affect how the method should be registered.
472    # Skip the check for overloaded constructors, since they don't support any
473    # of the extended attributes in question.
474    if not overloads[0].get('is_constructor'):
475        overload_extended_attributes = [
476            method['custom_registration_extended_attributes']
477            for method in overloads]
478        for extended_attribute in v8_methods.CUSTOM_REGISTRATION_EXTENDED_ATTRIBUTES:
479            if common_key(overload_extended_attributes, extended_attribute) is None:
480                raise ValueError('Overloads of %s have conflicting extended attribute %s'
481                                 % (name, extended_attribute))
482
483    # Check and fail if overloads disagree about whether the return type
484    # is a Promise or not.
485    promise_overload_count = sum(1 for method in overloads if method.get('idl_type') == 'Promise')
486    if promise_overload_count not in (0, len(overloads)):
487        raise ValueError('Overloads of %s have conflicting Promise/non-Promise types'
488                         % (name))
489
490    return {
491        'deprecate_all_as': common_value(overloads, 'deprecate_as'),  # [DeprecateAs]
492        'exposed_test_all': common_value(overloads, 'exposed_test'),  # [Exposed]
493        'has_custom_registration_all': common_value(overloads, 'has_custom_registration'),
494        'length_tests_methods': length_tests_methods(effective_overloads_by_length),
495        # 1. Let maxarg be the length of the longest type list of the
496        # entries in S.
497        'maxarg': lengths[-1],
498        'measure_all_as': common_value(overloads, 'measure_as'),  # [MeasureAs]
499        'minarg': lengths[0],
500        'per_context_enabled_function_all': common_value(overloads, 'per_context_enabled_function'),  # [PerContextEnabled]
501        'runtime_enabled_function_all': common_value(overloads, 'runtime_enabled_function'),  # [RuntimeEnabled]
502        'valid_arities': lengths
503            # Only need to report valid arities if there is a gap in the
504            # sequence of possible lengths, otherwise invalid length means
505            # "not enough arguments".
506            if lengths[-1] - lengths[0] != len(lengths) - 1 else None,
507    }
508
509
510def effective_overload_set(F):
511    """Returns the effective overload set of an overloaded function.
512
513    An effective overload set is the set of overloaded functions + signatures
514    (type list of arguments, with optional and variadic arguments included or
515    not), and is used in the overload resolution algorithm.
516
517    For example, given input [f1(optional long x), f2(DOMString s)], the output
518    is informally [f1(), f1(long), f2(DOMString)], and formally
519    [(f1, [], []), (f1, [long], [optional]), (f2, [DOMString], [required])].
520
521    Currently the optionality list is a list of |is_optional| booleans (True
522    means optional, False means required); to support variadics this needs to
523    be tri-valued as required, optional, or variadic.
524
525    Formally:
526    An effective overload set represents the allowable invocations for a
527    particular operation, constructor (specified with [Constructor] or
528    [NamedConstructor]), legacy caller or callback function.
529
530    An additional argument N (argument count) is needed when overloading
531    variadics, but we don't use that currently.
532
533    Spec: http://heycam.github.io/webidl/#dfn-effective-overload-set
534
535    Formally the input and output lists are sets, but methods are stored
536    internally as dicts, which can't be stored in a set because they are not
537    hashable, so we use lists instead.
538
539    Arguments:
540        F: list of overloads for a given callable name.
541
542    Returns:
543        S: list of tuples of the form (callable, type list, optionality list).
544    """
545    # Code closely follows the algorithm in the spec, for clarity and
546    # correctness, and hence is not very Pythonic.
547
548    # 1. Initialize S to ∅.
549    # (We use a list because we can't use a set, as noted above.)
550    S = []
551
552    # 2. Let F be a set with elements as follows, according to the kind of
553    # effective overload set:
554    # (Passed as argument, nothing to do.)
555
556    # 3. & 4. (maxarg, m) are only needed for variadics, not used.
557
558    # 5. For each operation, extended attribute or callback function X in F:
559    for X in F:  # X is the "callable", F is the overloads.
560        arguments = X['arguments']
561        # 1. Let n be the number of arguments X is declared to take.
562        n = len(arguments)
563        # 2. Let t0..n−1 be a list of types, where ti is the type of X’s
564        # argument at index i.
565        # (“type list”)
566        t = tuple(argument['idl_type_object'] for argument in arguments)
567        # 3. Let o0..n−1 be a list of optionality values, where oi is “variadic”
568        # if X’s argument at index i is a final, variadic argument, “optional”
569        # if the argument is optional, and “required” otherwise.
570        # (“optionality list”)
571        # (We’re just using a boolean for optional vs. required.)
572        o = tuple(argument['is_optional'] for argument in arguments)
573        # 4. Add to S the tuple <X, t0..n−1, o0..n−1>.
574        S.append((X, t, o))
575        # 5. If X is declared to be variadic, then:
576        # (Not used, so not implemented.)
577        # 6. Initialize i to n−1.
578        i = n - 1
579        # 7. While i ≥ 0:
580        # Spec bug (fencepost error); should be “While i > 0:”
581        # https://www.w3.org/Bugs/Public/show_bug.cgi?id=25590
582        while i > 0:
583            # 1. If argument i of X is not optional, then break this loop.
584            if not o[i]:
585                break
586            # 2. Otherwise, add to S the tuple <X, t0..i−1, o0..i−1>.
587            S.append((X, t[:i], o[:i]))
588            # 3. Set i to i−1.
589            i = i - 1
590        # 8. If n > 0 and all arguments of X are optional, then add to S the
591        # tuple <X, (), ()> (where “()” represents the empty list).
592        if n > 0 and all(oi for oi in o):
593            S.append((X, [], []))
594    # 6. The effective overload set is S.
595    return S
596
597
598def effective_overload_set_by_length(overloads):
599    def type_list_length(entry):
600        # Entries in the effective overload set are 3-tuples:
601        # (callable, type list, optionality list)
602        return len(entry[1])
603
604    effective_overloads = effective_overload_set(overloads)
605    return list(sort_and_groupby(effective_overloads, type_list_length))
606
607
608def distinguishing_argument_index(entries):
609    """Returns the distinguishing argument index for a sequence of entries.
610
611    Entries are elements of the effective overload set with the same number
612    of arguments (formally, same type list length), each a 3-tuple of the form
613    (callable, type list, optionality list).
614
615    Spec: http://heycam.github.io/webidl/#dfn-distinguishing-argument-index
616
617    If there is more than one entry in an effective overload set that has a
618    given type list length, then for those entries there must be an index i
619    such that for each pair of entries the types at index i are
620    distinguishable.
621    The lowest such index is termed the distinguishing argument index for the
622    entries of the effective overload set with the given type list length.
623    """
624    # Only applicable “If there is more than one entry”
625    assert len(entries) > 1
626    type_lists = [tuple(idl_type.name for idl_type in entry[1])
627                  for entry in entries]
628    type_list_length = len(type_lists[0])
629    # Only applicable for entries that “[have] a given type list length”
630    assert all(len(type_list) == type_list_length for type_list in type_lists)
631    name = entries[0][0].get('name', 'Constructor')  # for error reporting
632
633    # The spec defines the distinguishing argument index by conditions it must
634    # satisfy, but does not give an algorithm.
635    #
636    # We compute the distinguishing argument index by first computing the
637    # minimum index where not all types are the same, and then checking that
638    # all types in this position are distinguishable (and the optionality lists
639    # up to this point are identical), since "minimum index where not all types
640    # are the same" is a *necessary* condition, and more direct to check than
641    # distinguishability.
642    types_by_index = (set(types) for types in zip(*type_lists))
643    try:
644        # “In addition, for each index j, where j is less than the
645        #  distinguishing argument index for a given type list length, the types
646        #  at index j in all of the entries’ type lists must be the same”
647        index = next(i for i, types in enumerate(types_by_index)
648                     if len(types) > 1)
649    except StopIteration:
650        raise ValueError('No distinguishing index found for %s, length %s:\n'
651                         'All entries have the same type list:\n'
652                         '%s' % (name, type_list_length, type_lists[0]))
653    # Check optionality
654    # “and the booleans in the corresponding list indicating argument
655    #  optionality must be the same.”
656    # FIXME: spec typo: optionality value is no longer a boolean
657    # https://www.w3.org/Bugs/Public/show_bug.cgi?id=25628
658    initial_optionality_lists = set(entry[2][:index] for entry in entries)
659    if len(initial_optionality_lists) > 1:
660        raise ValueError(
661            'Invalid optionality lists for %s, length %s:\n'
662            'Optionality lists differ below distinguishing argument index %s:\n'
663            '%s'
664            % (name, type_list_length, index, set(initial_optionality_lists)))
665
666    # Check distinguishability
667    # http://heycam.github.io/webidl/#dfn-distinguishable
668    # Use names to check for distinct types, since objects are distinct
669    # FIXME: check distinguishability more precisely, for validation
670    distinguishing_argument_type_names = [type_list[index]
671                                          for type_list in type_lists]
672    if (len(set(distinguishing_argument_type_names)) !=
673        len(distinguishing_argument_type_names)):
674        raise ValueError('Types in distinguishing argument are not distinct:\n'
675                         '%s' % distinguishing_argument_type_names)
676
677    return index
678
679
680def length_tests_methods(effective_overloads_by_length):
681    """Returns sorted list of resolution tests and associated methods, by length.
682
683    This builds the main data structure for the overload resolution loop.
684    For a given argument length, bindings test argument at distinguishing
685    argument index, in order given by spec: if it is compatible with
686    (optionality or) type required by an overloaded method, resolve to that
687    method.
688
689    Returns:
690        [(length, [(test, method)])]
691    """
692    return [(length, list(resolution_tests_methods(effective_overloads)))
693            for length, effective_overloads in effective_overloads_by_length]
694
695
696def resolution_tests_methods(effective_overloads):
697    """Yields resolution test and associated method, in resolution order, for effective overloads of a given length.
698
699    This is the heart of the resolution algorithm.
700    http://heycam.github.io/webidl/#dfn-overload-resolution-algorithm
701
702    Note that a given method can be listed multiple times, with different tests!
703    This is to handle implicit type conversion.
704
705    Returns:
706        [(test, method)]
707    """
708    methods = [effective_overload[0]
709               for effective_overload in effective_overloads]
710    if len(methods) == 1:
711        # If only one method with a given length, no test needed
712        yield 'true', methods[0]
713        return
714
715    # 6. If there is more than one entry in S, then set d to be the
716    # distinguishing argument index for the entries of S.
717    index = distinguishing_argument_index(effective_overloads)
718    # (7-9 are for handling |undefined| values for optional arguments before
719    # the distinguishing argument (as “missing”), so you can specify only some
720    # optional arguments. We don’t support this, so we skip these steps.)
721    # 10. If i = d, then:
722    # (d is the distinguishing argument index)
723    # 1. Let V be argi.
724    #     Note: This is the argument that will be used to resolve which
725    #           overload is selected.
726    cpp_value = 'info[%s]' % index
727
728    # Extract argument and IDL type to simplify accessing these in each loop.
729    arguments = [method['arguments'][index] for method in methods]
730    arguments_methods = zip(arguments, methods)
731    idl_types = [argument['idl_type_object'] for argument in arguments]
732    idl_types_methods = zip(idl_types, methods)
733
734    # We can’t do a single loop through all methods or simply sort them, because
735    # a method may be listed in multiple steps of the resolution algorithm, and
736    # which test to apply differs depending on the step.
737    #
738    # Instead, we need to go through all methods at each step, either finding
739    # first match (if only one test is allowed) or filtering to matches (if
740    # multiple tests are allowed), and generating an appropriate tests.
741
742    # 2. If V is undefined, and there is an entry in S whose list of
743    # optionality values has “optional” at index i, then remove from S all
744    # other entries.
745    try:
746        method = next(method for argument, method in arguments_methods
747                      if argument['is_optional'])
748        test = '%s->IsUndefined()' % cpp_value
749        yield test, method
750    except StopIteration:
751        pass
752
753    # 3. Otherwise: if V is null or undefined, and there is an entry in S that
754    # has one of the following types at position i of its type list,
755    # • a nullable type
756    try:
757        method = next(method for idl_type, method in idl_types_methods
758                      if idl_type.is_nullable)
759        test = 'isUndefinedOrNull(%s)' % cpp_value
760        yield test, method
761    except StopIteration:
762        pass
763
764    # 4. Otherwise: if V is a platform object – but not a platform array
765    # object – and there is an entry in S that has one of the following
766    # types at position i of its type list,
767    # • an interface type that V implements
768    # (Unlike most of these tests, this can return multiple methods, since we
769    #  test if it implements an interface. Thus we need a for loop, not a next.)
770    # (We distinguish wrapper types from built-in interface types.)
771    for idl_type, method in ((idl_type, method)
772                             for idl_type, method in idl_types_methods
773                             if idl_type.is_wrapper_type):
774        test = 'V8{idl_type}::hasInstance({cpp_value}, info.GetIsolate())'.format(idl_type=idl_type.base_type, cpp_value=cpp_value)
775        yield test, method
776
777    # 8. Otherwise: if V is any kind of object except for a native Date object,
778    # a native RegExp object, and there is an entry in S that has one of the
779    # following types at position i of its type list,
780    # • an array type
781    # • a sequence type
782    # ...
783    # • a dictionary
784    #
785    # FIXME:
786    # We don't strictly follow the algorithm here. The algorithm says "remove
787    # all other entries" if there is "one entry" matching, but we yield all
788    # entries to support following constructors:
789    # [constructor(sequence<DOMString> arg), constructor(Dictionary arg)]
790    # interface I { ... }
791    # (Need to check array types before objects because an array is an object)
792    for idl_type, method in idl_types_methods:
793        if idl_type.native_array_element_type:
794            # (We test for Array instead of generic Object to type-check.)
795            # FIXME: test for Object during resolution, then have type check for
796            # Array in overloaded method: http://crbug.com/262383
797            yield '%s->IsArray()' % cpp_value, method
798    for idl_type, method in idl_types_methods:
799        if idl_type.is_dictionary or idl_type.name == 'Dictionary':
800            # FIXME: should be '{1}->IsObject() && !{1}->IsDate() && !{1}->IsRegExp()'.format(cpp_value)
801            # FIXME: the IsDate and IsRegExp checks can be skipped if we've
802            # already generated tests for them.
803            yield '%s->IsObject()' % cpp_value, method
804
805    # (Check for exact type matches before performing automatic type conversion;
806    # only needed if distinguishing between primitive types.)
807    if len([idl_type.is_primitive_type for idl_type in idl_types]) > 1:
808        # (Only needed if match in step 11, otherwise redundant.)
809        if any(idl_type.is_string_type or idl_type.is_enum
810               for idl_type in idl_types):
811            # 10. Otherwise: if V is a Number value, and there is an entry in S
812            # that has one of the following types at position i of its type
813            # list,
814            # • a numeric type
815            try:
816                method = next(method for idl_type, method in idl_types_methods
817                              if idl_type.is_numeric_type)
818                test = '%s->IsNumber()' % cpp_value
819                yield test, method
820            except StopIteration:
821                pass
822
823    # (Perform automatic type conversion, in order. If any of these match,
824    # that’s the end, and no other tests are needed.) To keep this code simple,
825    # we rely on the C++ compiler's dead code elimination to deal with the
826    # redundancy if both cases below trigger.
827
828    # 11. Otherwise: if there is an entry in S that has one of the following
829    # types at position i of its type list,
830    # • DOMString
831    # • ByteString
832    # • ScalarValueString [a DOMString typedef, per definition.]
833    # • an enumeration type
834    try:
835        method = next(method for idl_type, method in idl_types_methods
836                      if idl_type.is_string_type or idl_type.is_enum)
837        yield 'true', method
838    except StopIteration:
839        pass
840
841    # 12. Otherwise: if there is an entry in S that has one of the following
842    # types at position i of its type list,
843    # • a numeric type
844    try:
845        method = next(method for idl_type, method in idl_types_methods
846                      if idl_type.is_numeric_type)
847        yield 'true', method
848    except StopIteration:
849        pass
850
851
852################################################################################
853# Utility functions
854################################################################################
855
856def Counter(iterable):
857    # Once using Python 2.7, using collections.Counter
858    counter = defaultdict(lambda: 0)
859    for item in iterable:
860        counter[item] += 1
861    return counter
862
863
864def common(dicts, f):
865    """Returns common result of f across an iterable of dicts, or None.
866
867    Call f for each dict and return its result if the same across all dicts.
868    """
869    values = (f(d) for d in dicts)
870    first_value = next(values)
871    if all(value == first_value for value in values):
872        return first_value
873    return None
874
875
876def common_key(dicts, key):
877    """Returns common presence of a key across an iterable of dicts, or None.
878
879    True if all dicts have the key, False if none of the dicts have the key,
880    and None if some but not all dicts have the key.
881    """
882    return common(dicts, lambda d: key in d)
883
884
885def common_value(dicts, key):
886    """Returns common value of a key across an iterable of dicts, or None.
887
888    Auxiliary function for overloads, so can consolidate an extended attribute
889    that appears with the same value on all items in an overload set.
890    """
891    return common(dicts, lambda d: d.get(key))
892
893
894def sort_and_groupby(l, key=None):
895    """Returns a generator of (key, list), sorting and grouping list by key."""
896    l.sort(key=key)
897    return ((k, list(g)) for k, g in itertools.groupby(l, key))
898
899
900################################################################################
901# Constructors
902################################################################################
903
904# [Constructor]
905def constructor_context(interface, constructor):
906    # [RaisesException=Constructor]
907    is_constructor_raises_exception = \
908        interface.extended_attributes.get('RaisesException') == 'Constructor'
909
910    return {
911        'arguments': [v8_methods.argument_context(interface, constructor, argument, index)
912                      for index, argument in enumerate(constructor.arguments)],
913        'cpp_type': cpp_template_type(
914            cpp_ptr_type('RefPtr', 'RawPtr', gc_type(interface)),
915            cpp_name(interface)),
916        'cpp_value': v8_methods.cpp_value(
917            interface, constructor, len(constructor.arguments)),
918        'has_exception_state':
919            is_constructor_raises_exception or
920            any(argument for argument in constructor.arguments
921                if argument.idl_type.name == 'SerializedScriptValue' or
922                   argument.idl_type.v8_conversion_needs_exception_state),
923        'is_call_with_document':
924            # [ConstructorCallWith=Document]
925            has_extended_attribute_value(interface,
926                'ConstructorCallWith', 'Document'),
927        'is_call_with_execution_context':
928            # [ConstructorCallWith=ExecutionContext]
929            has_extended_attribute_value(interface,
930                'ConstructorCallWith', 'ExecutionContext'),
931        'is_constructor': True,
932        'is_named_constructor': False,
933        'is_raises_exception': is_constructor_raises_exception,
934        'number_of_required_arguments':
935            number_of_required_arguments(constructor),
936    }
937
938
939# [NamedConstructor]
940def named_constructor_context(interface):
941    extended_attributes = interface.extended_attributes
942    if 'NamedConstructor' not in extended_attributes:
943        return None
944    # FIXME: parser should return named constructor separately;
945    # included in constructors (and only name stored in extended attribute)
946    # for Perl compatibility
947    idl_constructor = interface.constructors[-1]
948    assert idl_constructor.name == 'NamedConstructor'
949    context = constructor_context(interface, idl_constructor)
950    context.update({
951        'name': extended_attributes['NamedConstructor'],
952        'is_named_constructor': True,
953    })
954    return context
955
956
957def number_of_required_arguments(constructor):
958    return len([argument for argument in constructor.arguments
959                if not argument.is_optional])
960
961
962def interface_length(interface, constructors):
963    # Docs: http://heycam.github.io/webidl/#es-interface-call
964    if 'EventConstructor' in interface.extended_attributes:
965        return 1
966    if not constructors:
967        return 0
968    return min(constructor['number_of_required_arguments']
969               for constructor in constructors)
970
971
972################################################################################
973# Special operations (methods)
974# http://heycam.github.io/webidl/#idl-special-operations
975################################################################################
976
977def property_getter(getter, cpp_arguments):
978    def is_null_expression(idl_type):
979        if idl_type.is_union_type:
980            notnull = ' || '.join([
981                    member_argument['null_check_value']
982                    for member_argument in idl_type.union_arguments])
983            return '!(%s)' % notnull
984        if idl_type.name == 'String':
985            return 'result.isNull()'
986        if idl_type.is_interface_type:
987            return '!result'
988        return ''
989
990    idl_type = getter.idl_type
991    extended_attributes = getter.extended_attributes
992    is_raises_exception = 'RaisesException' in extended_attributes
993
994    # FIXME: make more generic, so can use v8_methods.cpp_value
995    cpp_method_name = 'impl->%s' % cpp_name(getter)
996
997    if is_raises_exception:
998        cpp_arguments.append('exceptionState')
999    union_arguments = idl_type.union_arguments
1000    if union_arguments:
1001        cpp_arguments.extend([member_argument['cpp_value']
1002                              for member_argument in union_arguments])
1003
1004    cpp_value = '%s(%s)' % (cpp_method_name, ', '.join(cpp_arguments))
1005
1006    return {
1007        'cpp_type': idl_type.cpp_type,
1008        'cpp_value': cpp_value,
1009        'is_custom':
1010            'Custom' in extended_attributes and
1011            (not extended_attributes['Custom'] or
1012             has_extended_attribute_value(getter, 'Custom', 'PropertyGetter')),
1013        'is_custom_property_enumerator': has_extended_attribute_value(
1014            getter, 'Custom', 'PropertyEnumerator'),
1015        'is_custom_property_query': has_extended_attribute_value(
1016            getter, 'Custom', 'PropertyQuery'),
1017        'is_enumerable': 'NotEnumerable' not in extended_attributes,
1018        'is_null_expression': is_null_expression(idl_type),
1019        'is_raises_exception': is_raises_exception,
1020        'name': cpp_name(getter),
1021        'union_arguments': union_arguments,
1022        'v8_set_return_value': idl_type.v8_set_return_value('result', extended_attributes=extended_attributes, script_wrappable='impl', release=idl_type.release),
1023    }
1024
1025
1026def property_setter(setter):
1027    idl_type = setter.arguments[1].idl_type
1028    extended_attributes = setter.extended_attributes
1029    is_raises_exception = 'RaisesException' in extended_attributes
1030    return {
1031        'has_type_checking_interface':
1032            has_extended_attribute_value(setter, 'TypeChecking', 'Interface') and
1033            idl_type.is_wrapper_type,
1034        'idl_type': idl_type.base_type,
1035        'is_custom': 'Custom' in extended_attributes,
1036        'has_exception_state': (is_raises_exception or
1037                                idl_type.v8_conversion_needs_exception_state),
1038        'is_raises_exception': is_raises_exception,
1039        'name': cpp_name(setter),
1040        'v8_value_to_local_cpp_value': idl_type.v8_value_to_local_cpp_value(
1041            extended_attributes, 'v8Value', 'propertyValue'),
1042    }
1043
1044
1045def property_deleter(deleter):
1046    idl_type = deleter.idl_type
1047    if str(idl_type) != 'boolean':
1048        raise Exception(
1049            'Only deleters with boolean type are allowed, but type is "%s"' %
1050            idl_type)
1051    extended_attributes = deleter.extended_attributes
1052    return {
1053        'is_custom': 'Custom' in extended_attributes,
1054        'is_raises_exception': 'RaisesException' in extended_attributes,
1055        'name': cpp_name(deleter),
1056    }
1057
1058
1059################################################################################
1060# Indexed properties
1061# http://heycam.github.io/webidl/#idl-indexed-properties
1062################################################################################
1063
1064def indexed_property_getter(interface):
1065    try:
1066        # Find indexed property getter, if present; has form:
1067        # getter TYPE [OPTIONAL_IDENTIFIER](unsigned long ARG1)
1068        getter = next(
1069            method
1070            for method in interface.operations
1071            if ('getter' in method.specials and
1072                len(method.arguments) == 1 and
1073                str(method.arguments[0].idl_type) == 'unsigned long'))
1074    except StopIteration:
1075        return None
1076
1077    return property_getter(getter, ['index'])
1078
1079
1080def indexed_property_setter(interface):
1081    try:
1082        # Find indexed property setter, if present; has form:
1083        # setter RETURN_TYPE [OPTIONAL_IDENTIFIER](unsigned long ARG1, ARG_TYPE ARG2)
1084        setter = next(
1085            method
1086            for method in interface.operations
1087            if ('setter' in method.specials and
1088                len(method.arguments) == 2 and
1089                str(method.arguments[0].idl_type) == 'unsigned long'))
1090    except StopIteration:
1091        return None
1092
1093    return property_setter(setter)
1094
1095
1096def indexed_property_deleter(interface):
1097    try:
1098        # Find indexed property deleter, if present; has form:
1099        # deleter TYPE [OPTIONAL_IDENTIFIER](unsigned long ARG)
1100        deleter = next(
1101            method
1102            for method in interface.operations
1103            if ('deleter' in method.specials and
1104                len(method.arguments) == 1 and
1105                str(method.arguments[0].idl_type) == 'unsigned long'))
1106    except StopIteration:
1107        return None
1108
1109    return property_deleter(deleter)
1110
1111
1112################################################################################
1113# Named properties
1114# http://heycam.github.io/webidl/#idl-named-properties
1115################################################################################
1116
1117def named_property_getter(interface):
1118    try:
1119        # Find named property getter, if present; has form:
1120        # getter TYPE [OPTIONAL_IDENTIFIER](DOMString ARG1)
1121        getter = next(
1122            method
1123            for method in interface.operations
1124            if ('getter' in method.specials and
1125                len(method.arguments) == 1 and
1126                str(method.arguments[0].idl_type) == 'DOMString'))
1127    except StopIteration:
1128        return None
1129
1130    getter.name = getter.name or 'anonymousNamedGetter'
1131    return property_getter(getter, ['propertyName'])
1132
1133
1134def named_property_setter(interface):
1135    try:
1136        # Find named property setter, if present; has form:
1137        # setter RETURN_TYPE [OPTIONAL_IDENTIFIER](DOMString ARG1, ARG_TYPE ARG2)
1138        setter = next(
1139            method
1140            for method in interface.operations
1141            if ('setter' in method.specials and
1142                len(method.arguments) == 2 and
1143                str(method.arguments[0].idl_type) == 'DOMString'))
1144    except StopIteration:
1145        return None
1146
1147    return property_setter(setter)
1148
1149
1150def named_property_deleter(interface):
1151    try:
1152        # Find named property deleter, if present; has form:
1153        # deleter TYPE [OPTIONAL_IDENTIFIER](DOMString ARG)
1154        deleter = next(
1155            method
1156            for method in interface.operations
1157            if ('deleter' in method.specials and
1158                len(method.arguments) == 1 and
1159                str(method.arguments[0].idl_type) == 'DOMString'))
1160    except StopIteration:
1161        return None
1162
1163    return property_deleter(deleter)
1164