Modifying the sources

cmello edited this page Mar 20, 2012 · 13 revisions

TDD

Before fixing a bug, please make sure that there is a failing RubySpec spec for the defect. Do validate that it works with MRI first because RubySpec occasionally has incorrect specs (typically because there is a difference in behavior between Windows and other platforms).

Also, make sure that the spec tests all the corner cases like nil arguments, arguments that can be converted via to_<type> methods like to_s, etc. See the RubySpec Style Guide for a list of corner cases to think about.

Coding conventions

  • Brace indentation is based on K&R style. Opening braces for class or method declaration are on the same line as the class or method declaration.
  • Use .NET Framework conventions for all identifiers.
    • There is no specific guideline for naming private fields in this document; we prefix field names with underscores (e.g. private string _fooBar;) so that use of the fields is easily distinguishable as a field access as opposed to a local variable access.
    • If you’re not sure about some convention try to find out in the rest of the IronRuby code or ask in the list.
  • Use /*!*/ for method parameters and instance fields that should never be null. Spec# annotations.
  • Do not use public fields (Base::algorithm, buffer). Use properties if it is necessary to expose the field or private/internal visibility otherwise.
  • Use readonly if the field is not mutated after the object is constructed.

Changes in RubySpec

You can include changes to the RubySpec specs with your commits. The IronRuby team will push the changes to the RubySpec project.See the RubySpec wiki, especially the Style Guide.

IronRuby engine requirements

Adding a new Ruby library class or method

  1. Add a regular C# class or method, and mark it with [RubyClass("ClassName")] or [RubyMethod("method_name")].
  2. Compile (alias is brbd)
  3. Run geninit to generate C# initialization code into c:\path\to\Merlin\Main\Languages\Ruby\Libraries.LCA_RESTRICTED\Initializers.Generated.cs.
  4. Compile again

Note that if you change the signature of an existing method already marked with RubyMethodAttribute, compilation will fail as Initializers.Generated.cs will have initialization code for the old signature. In this case, you can edit the initialization code by hand to match the new signature. However, an easier solution is to delete all the contents of Initializers.Generated.cs (but not the file itself), compile, run geninit, and compile again. This requires an extra compilation step, but avoids the manual updating of the initialization code.

Order of parameters for a [RubyMethod] method:

  1. 0 or more CallSiteStorage – if the library method needs to call back into Ruby code. Some predefined instantiations like BinaryOpStorage are in Merlin\Main\Languages\Ruby\Ruby\Runtime\CallSiteStorages.cs
  2. 0 or 1 of either of RubyContext/*!*/ context or RubyClass/*!*/ self for instance methods – this can be used to access information specific to the current ScriptRuntime like RubyContext.KCode. Note that if the method has a RubyClass or BlockParam parameter, it is not necessary to have a RubyContext parameter as it can be accessed with RubyClass.Context or BlockParam.Context.
  3. 0 or 1 of BlockParam – if the method accepts a block.
  4. 1 of object self – This will be the receiver object for instance methods, and a RubyClass instance for class methods
  5. Remaining user-visible parameters

Declarative annotations

  1. Parameters should be as strongly-typed as possible. For example, use “MutableString arg” if the Ruby method accepts a Ruby String parameter.
    1. Do not do the type check imperatively in the code with a cast, even for error handling. If the C# methods are properly typed, the IronRuby engine should throw the right error message if the user passes in an incorrect argument type.
  2. Using multiple C# overloads – If a Ruby method accepts either Ruby String or Fixnum, use two C# overloads with MutableString and System.Int32. The IronRuby engine will make this visible as a single Ruby method
  3. Automatic conversion – the IronRuby engine will automatically call to_s on the object if the parmeter is declared as “[DefaultProtocol]MutableString arg”. Such standard conversions exist for:
    1. String-like objects (String or objects responding to to_s)
    2. Fixnum-like objects (Fixnum, or objects responding to to_i)
    3. Number-like objects (Fixnum, Bignum, or objects responding to to_i)
    4. Float-like objects (Float, or objects responding to to_f)
  4. [NotNull] – The IronRuby engine will throw the appropriate error message if the user passes in nil, but the Ruby method does not allow nil. This should also be used with BlockParam if the block is required. There is also [NotNullItems] which can be used for array parameters to indicate that the array elements should all be non-null

Here is a sample library method implementation:

[RubyClass("Fixnum", Extends = typeof(int), Inherits = typeof(Integer))]
public static class FixnumOps {
    ...
    [RubyMethod("<")]
    public static bool LessThan(
        BinaryOpStorage/*!*/ coercionStorage,
        BinaryOpStorage/*!*/ comparisonStorage,
        RubyContext/*!*/ context, 
        object self, 
        object other) {

        return Protocols.CoerceAndRelate(coercionStorage, comparisonStorage, "<", context, self, other);
    }
    ...
}

Here is a video to see the workflow of a simple bug fix (may be out-dated).

Validating the changes

The following commands will build and run all the tests that are required to pass. If you get any failures, do report them to the mailing-list to see if they are expected or not. This command can usually run without any failures.

irtests

IronPython tests

r
cd Test
msbuild TestRunner\TestRunner.sln
test-ipy /all