Code Review: NewMethodBinder

Tfpt review /shelveset:NewMethodBinder;tomat

Affects outer DLR, Python, Ruby.

DLR, Python

Redesigns the default method binder:

  •      Merges ParameterBinder into MethodBinder, moves virtual 
    

methods related to CLR method binding and parameter conversions from
ActionBinder and DefaultBinder to MethodBinder.

  •      MethodBinder is now an abstract class that provides virtuals 
    

that languages can override. DefaultMethodBinder provides simple
implementation that languages can base their binders on or directly use.

  •      Enables "partially restricted splatting" feature (only 
    

implemented in Ruby so far, but should be relatively easy to generalize
it and use it by default):

  •      Previous implementation required languages to pass the binder 
    

an array of meta-objects including all items of the splatted array. This
is not feasible for splatting arrays containing thousands of items
(StackOverflow exception occurred in a Ruby library due to an expression
of depth greater than 5k constructed as a rule condition). The new
implementation allows the binder to only construct such conditions and
restrictions that are necessary to decide which overload to select. In
the worse case, which occurs e.g. when the overload set is { foo(params
int[]), foo(params object[]) } and the call-site is foo(*([1]*10000 +
[‘x’])) , the condition that decides whether an overload/rule is
applicable is implemented using a loop rather than disjunction of 10001
type equality expressions. Usually, the overload could be selected by
splatting the array partially, using few items of the array from
beginning and/or end of the array. More optimizations to the partial
splatting are certainly possible - those should be easy to implement on
top of the new method binder architecture if needed.

  •      MethodBinder builds its state in several steps (see 
    

ResolveOverload method):

  1.  TargetSets and MethodCadnidates are built - MethodCandidate 
    

represents a CLR method overload tailored to a fixed arity (a follow-up
refactoring will merge MethodTarget into MethodCandidate, they map 1:1).
TargetSet represents a set of MethodCandidates (CanidateSet would be a
better name - will rename in a follow-up change) that match the same
arity. Building MethodCandidates involve creating ParameterWrappers and
ArgBuilders. This process can be customized by languages. Languages can
define their own wrappers and builders, their own dictionary and array
splatting mechanisms.

  1.  The ActualArguments class is created (this is also customizable 
    

by languages). ActualArguments instance holds on meta-objects of
positional and named arguments, actual argument names, and various
indices needed for splatting.

  1.  TargetSet with arity corresponding to the number of actual 
    

arguments is created. If the binder implements partial splatting, the
array is splatted to match arities of existing target sets but no more.
The rest of the arguments (those that were not extracted from the array)
are referred to as “collapsed” and are dealt with separately during
overload resolution. The previous implementation of “MakeBindingTarget”
is replaced by a new one, which fixes issues with named arguments and
also takes collapsed arguments into consideration. It has several steps:

a. Match actual names of the arguments with parameter names,
calculate permutations that map indices of the former to the latter, and
filter out MethodCandidates whose parameter names don’t match. The
result of this step is a set of ApplicableCandidates, each
ApplicableCandidate comprises of a MethodCandidate and a ArgumentBinding
permutation.

b. Then for each narrowing level: Non-collapsed arguments are
tested for convertibility to the parameter types of the applicable
overloads. Those overloads that don’t match are removed from applicable
candidates set. If no overloads remain, it’s an error. If a single
overload remains we successfully chosen the overload (note that
collapsed arguments don’t need to be visited in this case). Otherwise we
check whether collapsed arguments can all be converted to the element
type of the corresponding params-array. Again, if no candidate remains,
we failed. If a single one remains we succeeded. Otherwise we continue
by comparing various conversions and selecting the best ones based on
the current narrowing level.

  •      During these steps the MethodBinder builds its state 
    

(candidate sets, actual arguments, etc.) and derived method binders can
also remember some additional data/state that is used for building
arguments or error reporting (temporary variable that stores the
splattee, restrictions/conditions used for splatting, the signature of
the call-site, etc.). The MethodBinder should be instantiated with all
information that doesn’t change during overload resolution (the “input”
to the overload resolution).

  •      Renames ParameterBinderWithCodeContext to PythonMethodBinder 
    

and moves it to Python.

  •      Moves SiteLocalStorage to Python. Ruby uses a different 
    

storage class and it’s very simple for languages to define and use their
own.

  •      ActionBinder.BindSpecialParameter shouldn't deal with 
    

CodeContext and SiteLocalStorage - moved to PythonMethodBinder.

  •      Moved ContextArgBuilder and SiteLocalStorageArgbuilder to 
    

Python.

  •      ArgBuilder.CanGenerateDelegate needs to be protected so that 
    

custom ArgBuilders can override it.

  •      Improves errors reported by the method binder.
    
  •      Removes old call, invoke  and operation actions and related 
    

code.

Ruby:

  •      Replaces calls to obsolete methods of default method binder 
    

with those using meta-objects.

  •      Implements partially restricted splatting.
    

Fixes
http://ironpython.codeplex.com/WorkItem/View.aspx?WorkItemId=18379.