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