Code Review: MethodBinder23

tfpt review “/shelveset:MethodBinder23;REDMOND\tomat”

Ruby changes

This change significantly affects Ruby method definitions in C#

  1. CodeContext hidden parameter is replaced with RubyScope or
    RubyContext hidden parameters depending on what the method needs.
    CodeContext is still supported but will eventually be dropped. Methods
    shouldn’t use any special parameters if they don’t need to. Methods
    should use RubyContext if they only need to get the current runtime
    context (note that you can get to the runtime context also from non-null
    BlockParam, RubyClass, RubyModule, and other runtime-bound Ruby
    objects). Currently we have 2 contexts: RubyExecutionContext and
    RubyContext. RubyExecutionContext is going to be merged into RubyContext
    soon. For now you can get it from RubyContext using ExecutionContext
    property. Methods that need to access the current scope or need to call
    a dynamic site need RubyScope. RubyScope holds on
    RubyContext/RubyExecutionContext so it is not necessary for a method to
    take both RubyScope and RubyContext parameters. In future dynamic sites
    will be passed in a special hidden parameter(s).

  2. The shelveset introduces DefaultProtocol attribute that can be
    applied on parameters of type MutableString and Int32 (more types will
    be supported in future). This feature removes the need to explicitly
    call Protocols.CastToString and Protocols.CastToFixnum as the binder
    performs the conversions for you. The only method that uses the
    attribute so far is String#center. However, most of the methods that use
    CastToString/Fixnum will be switched to DefaultProtocol eventually.

  3. BlockParam parameter now precedes self parameter and [NotNull]
    attribute is used for overload resolution if applied on BlockParam. A
    method that doesn’t include BlockParam can still be called with a block.
    The block is ignored in this case.

If the method overload doesn’t have a BlockParam parameter, we inject
MissingBlockParam parameter and arg builder.
The parameter is treated as a regular explicit mandatory parameter.

The argument builder provides no value for the actual argument
expression, which makes the default binder to skip it
when emitting a tree for the actual method call (this is necessary since
the method doesn’t in fact have the parameter).

By injecting the missing block parameter we achieve that all overloads
have either BlockParam, [NotNull]BlockParam or
MissingBlockParam parameter. MissingBlockParam (MBP) and BlockParam (BP)
are convertible to each other. Default binder prefers
those overloads where no conversion needs to happen, which ensures the
desired semantics:

Conversions with desired priority (the less number the higher priority):

Parameters: call w/o block call with non-null block
call with null block
(implicit, MBP, … ) MBP -> MBP (1) BP -> MBP (3)
BP -> MBP (2)
(implicit, BP, … ) MBP -> BP (2) BP -> BP (2)
BP -> BP (1)
(implicit, BP!, … ) N/A BP -> BP! (1)

  1. Methods designated by RubyConstructor attribute must have RubyClass
    self parameter. The class object being instantiated is passed in it.

  2. A RubyConstant attribute can now be applied on a method. The method
    is called during class initialization to produce the value of the

Many library methods were adjusted to match the binder changes.
Adds more checks to class-init generator to enforce correct method
Also adds library data dictionary to RubyContext - libraries can add
objects to the dictionary that should be associated with the context.

Fixes bugs in Struct:

  • Struct defines a singleton method “new” that is used to create new
    structures (classes).
  • If Singleton(Struct)#new method is removed, Class#new is invoked
    instead, which raises missing allocator error.
  • A class created by Singleton(Struct)#new is a structure class that
    defines accessors specified in struct constructor and its singleton
    class defines “[]”, “members” and “new” methods. “new” create an
    instance of the structure given values for the struct’s attributes.
  • Structures can be derived from and duplicated.

DLR changes

Renames MethodBinderContext to ParameterBinder and removes a dependency
on CodeContext from it. A subclass of ParameterBinder,
ParameterBinderWithCodeContext adds CodeContext expression to
ParameterBinder. The idea is that this class will eventually move to
Python and become PythonParameterBinder and the default binder won’t
depend on CodeContext. Methods on ActionBinder that deal with parameters
could also be moved to ParameterBinder then (e.g.
GetByRefArrayExpression, ParametersEquivalent, CanConvertFrom,
BindSpecialArgument, PrepareArgumentBinding). These methods cannot be
moved now since the parameter binder is not flown to all places where it
would be needed. Adds RubyParameterBinder which implements custom
parameter conversions for Ruby library methods.

Adds more methods to ActionBinder (should move on ParameterBinder in

  •   PrepareArgumentBinding, BindSpecialArgument - used in 

MethodBinder when the method candidates are built up. These extension
points enable to preprocess parameter infos and produce language
specific ParameterWrappers and ArgBuilders and skip initial language
specific hidden arguments. Ruby uses this to process hidden arguments of
types RubyScope, RubyContext and CodeContext (which is deprecated and
will be gradually removed from libraries). Also passing blocks to
library methods are handled in a special way: Most of the library
methods can accept a block even though they ignore it. It would be
tedious to use the block parameter when not needed and therefore the
Ruby binder injects it automatically to the ParameterWrappers and
ArgBuilders if not specified. The injected parameter is of type
MissingBlockParam and implicit conversions are defined from BlockParam
to MissingBlockParam and vice versa. Therefore, for the purpose of
overload resolution, each method has a block parameter. If no block is
specified at call-site overloads with MissingBlockParam are preferred,
followed by nullable BlockParam ([NotNull]BlockParam overloads are not
applicable). If a block is specified but is null then nullable
BlockParam has precedence over MissingBlockParam ([NotNull]BlockParam
overloads are not applicable). If a block is specified and is not null
then the order is [NotNull]BlockParam > BlockParam > MissingBlockParam.

  •   CanConvertFrom - moved here from ParameterWrapper and changed 

the parameter to ParameterWrappers instead of Type. This allows to
customize conversions based upon attributes and other modifiers applied
on the parameters in addition to the type of the parameter.

  •   ParametersEquivalent - used from ParameterWrapper to detect 

whether parameters are equivalent in parameter ordering used for
overload resolution.

Adds Candidate enum with values { Equivalent, One, Two} and refactors
various CompareTo, PreferConvert, SelectBestConversionFrom methods to
return values of Candidate enum instead of integers.

Adds ParameterInfo property to both ParameterWrapper and ArgBuilder. The
value is optional and some kinds of parameters and arg builders might
not be associated with parameter-info. The purpose is to allow parameter
comparison and conversion methods to make decisions based upon
parameter-info properties when available.

Deletes Dynamic class. It’s not used anywhere.

Minor changes:

  •   Refactored MethodTarget.MakeExpression.
  •   Renamed PrepareArgumentBinding to PrepareParameterBinding, 

BindSpecialArgument to BindSpecialParameter.

  •   Renamed CompareTo methods on ParameterWrapper to 

GetPreferredParameter(s), made them static and renamed their arguments
so that the value of Candidate enum expresses the preference that is
made by the method.

  •   Refactored usage of nullable Candidate enum, replaced it by 

Candidate.Ambiguous enum value. I’ve also added extension methods for
the enum that makes the code working with Candidate enum more readable.

  •   Removed type parameter from *ArgBuilder ctors where possible, 

add ctor overloads for callers that pass no parameter info.

  •   Removed #if BUG - I forgot that in the source code.
  •   Added comments where missing.

In addition, I’ve moved classes that are related to overload resolution
implementation in default binder from Generation into Actions.Calls

Python changes

Pass parameter binder with code context to default binder instead of
code context.