blob: 738725a5b5fd8d176506b196462974811710056b [file] [log] [blame]
# Copyright 2015 Google Inc. All Rights Reserved.
# coding=utf-8
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Functions for generating bindings for overloaded operations/constructors."""
from functools import partial
from v8_interface import distinguishing_argument_index
from v8_interface import effective_overload_set_by_length
def get_overload_contexts(expression_generator, method_overloads):
"""Create overload contexts for overloaded methods/constructors.
Arguments:
expression_generator: An ExpressionGenerator instance.
method_overloads: [overload_sets] where overload_sets is itself
a list of method_contexts (or constructor_contexts). All methods
in the list of method_contexts are overloads of the same symbol.
Returns:
[overload_contexts]
"""
overload_contexts = []
for overloaded_methods in method_overloads:
# Create an overload context. Binding generation will use this as a top
# level context, and the identifier will be bound to an overload
# resolution function.
overload_context = {
'overloads': overloaded_methods,
}
# Add a property to the overloaded methods indicating their overload
# indices. This allows us to refer to each overload with a unique name.
for index, method in enumerate(overloaded_methods, 1):
method['overload_index'] = index
# Get the effective overload set, and group by the number of arguments.
# This is a list of tuples (numargs, [overload_set])
effective_overloads_by_length = (
effective_overload_set_by_length(overloaded_methods))
overload_context['length'] = min(
[length for length, _ in effective_overloads_by_length])
# The first four steps of the overload resolution algorithm involve
# removing entries from the set S of all overloads where the number
# of arguments in the overload does not match the number of overloads
# passed to the function.
# We accomplish this by partitioning the set S into S0...S{maxarg} where
# each S{n} contains overloads with n arguments, and then switch on
# the number of arguments in the bindings code.
#
# http://heycam.github.io/webidl/#dfn-overload-resolution-algorithm
# 1. Let maxarg be the length of the longest type list of the entries
# in S.
# 2. Initialize argcount to be min(maxarg, n).
# 3. Remove from S all entries whose type list is not of length
# argcount.
#
# overload_resolution_context_by_length will be a list of tuples
# (length, distinguishing_index, [overload_resolution_tests])
overload_resolution_context_by_length = []
for length, overload_set in effective_overloads_by_length:
# For each overload, create a list of tuples for resolution:
# (length, distinguishing_argument_index, [resolution_tests])
# where the length refers to the number of arguments for the set Sn of
# overloads that take that number of arguments,
# distinguishing_argument_index is as defined in the spec (except we
# use None in the case that the size of the set Sn is 1), and
# resolution tests is a list of tuples:
# (test_callable, method_context)
# where test_callable is a function that takes one argument which is a
# string representing the name of a variable in the generated bindings
# code, and returns a boolean expression. If this evaluates to true,
# then the test's associated overloaded method_context should be called.
#
# http://heycam.github.io/webidl/#dfn-overload-resolution-algorithm
# 7. If there is more than one entry in S, then set d to be the
# distinguishing argument index for the entries of S.
overload_resolution_context_by_length.append(
(length, distinguishing_argument_index(overload_set)
if len(overload_set) > 1 else None,
list(resolution_tests_methods(expression_generator, overload_set))))
overload_context['overload_resolution_by_length'] = (
overload_resolution_context_by_length)
overload_contexts.append(overload_context)
return overload_contexts
def resolution_tests_methods(expression_generator, effective_overloads):
"""Yields resolution test and associated method.
Tests and methods are in resolution order for effective overloads of a given
length.
This is the heart of the resolution algorithm.
http://heycam.github.io/webidl/#dfn-overload-resolution-algorithm
This function is based off of v8_interface.resolution_tests_methods. In Blink
the first item in the tuple is a string representing C++ code that can be
executed to perform the test. In Cobalt it is a function that takes one
argument which is a string representing the variable name of a C++ object
on which the test should be performed, and returns an expression that
performs the test.
Args:
expression_generator: An ExpressionGenerator instance.
effective_overloads: List of tuples returned from
effective_overload_set_by_length
Yields:
(test, method)
"""
methods = [
effective_overload[0] for effective_overload in effective_overloads
]
if len(methods) == 1:
# If only one method with a given length, no test needed
yield lambda arg: 'true', methods[0]
return
# 6. If there is more than one entry in S, then set d to be the
# distinguishing argument index for the entries of S.
index = distinguishing_argument_index(effective_overloads)
# (7-9 are for handling |undefined| values for optional arguments before
# the distinguishing argument (as "missing"), so you can specify only some
# optional arguments. We don't support this, so we skip these steps.)
# 10. If i = d, then:
# (d is the distinguishing argument index)
# 1. Let V be argi.
# Note: This is the argument that will be used to resolve which
# overload is selected.
# js_value = 'info[%s]' % index
# Extract argument and IDL type to simplify accessing these in each loop.
arguments = [method['arguments'][index] for method in methods]
arguments_methods = zip(arguments, methods)
idl_types = [argument['idl_type_object'] for argument in arguments]
idl_types_methods = zip(idl_types, methods)
# We can't do a single loop through all methods or simply sort them, because
# a method may be listed in multiple steps of the resolution algorithm, and
# which test to apply differs depending on the step.
#
# Instead, we need to go through all methods at each step, either finding
# first match (if only one test is allowed) or filtering to matches (if
# multiple tests are allowed), and generating an appropriate tests.
# 2. If V is undefined, and there is an entry in S whose list of
# optionality values has "optional" at index i, then remove from S all
# other entries.
try:
method = next(method for argument, method in arguments_methods
if argument['is_optional'])
yield expression_generator.is_undefined, method
except StopIteration:
pass
# 3. Otherwise: if V is null or undefined, and there is an entry in S that
# has one of the following types at position i of its type list,
# - a nullable type
try:
method = next(method for idl_type, method in idl_types_methods
if idl_type.is_nullable)
yield expression_generator.is_undefined_or_null, method
except StopIteration:
pass
# 4. Otherwise: if V is a platform object - but not a platform array
# object - and there is an entry in S that has one of the following
# types at position i of its type list,
# - an interface type that V implements
# (Unlike most of these tests, this can return multiple methods, since we
# test if it implements an interface. Thus we need a for loop, not a next.)
# (We distinguish wrapper types from built-in interface types.)
for idl_type, method in ((idl_type, method)
for idl_type, method in idl_types_methods
if idl_type.is_wrapper_type):
base_type = idl_type.base_type if idl_type.is_nullable else idl_type
yield partial(expression_generator.inherits_interface, base_type), method
# 8. Otherwise: if V is any kind of object except for a native Date object,
# a native RegExp object, and there is an entry in S that has one of the
# following types at position i of its type list,
# - an array type
# - a sequence type
# ...
# - a dictionary
#
# FIXME:
# We don't strictly follow the algorithm here. The algorithm says "remove
# all other entries" if there is "one entry" matching, but we yield all
# entries to support following constructors:
# [constructor(sequence<DOMString> arg), constructor(Dictionary arg)]
# interface I { ... }
# (Need to check array types before objects because an array is an object)
for idl_type, method in idl_types_methods:
if idl_type.native_array_element_type:
raise NotImplementedError('Array types not yet supported')
for idl_type, method in idl_types_methods:
if idl_type.is_dictionary or idl_type.name == 'Dictionary':
raise NotImplementedError('Dictionary types not yet supported.')
# (Check for exact type matches before performing automatic type conversion;
# only needed if distinguishing between primitive types.)
if len([idl_type.is_primitive_type for idl_type in idl_types]) > 1:
# (Only needed if match in step 11, otherwise redundant.)
if any(idl_type.is_string_type or idl_type.is_enum
for idl_type in idl_types):
# 10. Otherwise: if V is a Number value, and there is an entry in S
# that has one of the following types at position i of its type
# list,
# - a numeric type
try:
method = next(method for idl_type, method in idl_types_methods
if idl_type.is_numeric_type)
yield (expression_generator.is_number), method
except StopIteration:
pass
# (Perform automatic type conversion, in order. If any of these match,
# that's the end, and no other tests are needed.) To keep this code simple,
# we rely on the C++ compiler's dead code elimination to deal with the
# redundancy if both cases below trigger.
# 11. Otherwise: if there is an entry in S that has one of the following
# types at position i of its type list,
# - DOMString
# - ByteString
# - USVString
# - an enumeration type
try:
method = next(method for idl_type, method in idl_types_methods
if idl_type.is_string_type or idl_type.is_enum)
yield lambda arg: 'true', method
except StopIteration:
pass
# 12. Otherwise: if there is an entry in S that has one of the following
# types at position i of its type list,
# - a numeric type
try:
method = next(method for idl_type, method in idl_types_methods
if idl_type.is_numeric_type)
yield lambda arg: 'true', method
except StopIteration:
pass