StaDyn

Extending Dynamic features of the SSCLI

The StaDyn syntax is based on C# 3.0. We can specify type of references explicitly or use the var keyword to use implicitly typed references. However, var references of StaDyn are much more powerful that implicitly typed local variables in C# 3.0.

References may have multiple types within a scope

Static typing commonly forces a variable of type T to have the same type within the scope in which it is bound to a value. However, this is not the common behavior in dynamic languages. We can use one reference to hold different types in the same scope, as dynamic languages do. However, static type checking is performed taking into account the concrete type of the reference.

        using System;
        class Test {
            public static void Main() {
                Console.Write("Your age, please: ");
                var age = Console.In.ReadLine();
                Console.WriteLine("You are " + age + " years old.");

                age = Convert.ToInt32(age);
                Console.WriteLine(age.GetType());
                age++;
                Console.WriteLine("Happy birthday, you are " + age + " years old now.");
        int length = age.Length; // * Compilation error
    }
}
                

Correct static type inference is shown with the compilation error detected in the last line of code. Moreover, the best possible runtime performance is obtained because we do not need to use reflection to discover types at runtime.

Static Duck Typing

The static type system of StaDyn is flow-sensitive. This means that it takes into account the flow context of each var reference. It gathers concrete type information (opposite to classic abstract type systems): StaDyn knows all the possible types of a var reference may have. Following this scheme, the language offers a compile-time duck typing. If a var reference points to a set of objects and all of them have a public m method, it can be invoked. The objects need not to implement a common interface or class with the m method.

        var exception;
        if (new Random().NextDouble()<0.5)
            exception = new ApplicationException("An application exception.");
        else
            exception = new SystemException("A system exception");
        Console.WriteLine(exception.Message);
                

In the code above, exception may point to an ApplicationException or to a SystemException object. Both objects have the Message property and, therefore, it is safe to use this property. It is not necessary to define a common interface or class to pass this message. Since type inference system is flow-sensitive and uses concrete types, the programmer obtains a type safe duck-typing system.

Separation of the Dynamism Concern

If we add another alternative in the assignment of the exception reference:

       var exception;
        Random random = new Random();
       switch (random.Next(1,4)) {
       case 1: 
            exception = new ApplicationException("An application exception.");
            break;
       case 2:
            exception = new SystemException("A system exception");
            break;
       case 3:
            exception = "This is not an exception";
            break;
        }
        Console.WriteLine(exception.ToString());
        Console.WriteLine(exception.Message); // * Compilation error?
                

The ToString message is correct because it is offered by the three possible objects created. However, the Message property depends on the level of dynamism we require. By default, the compiler uses the everythingStatic option, and the following error message is shown:

        C:\...> sample3.cs(21.37): Error ErrorManagement.UnknownMemberError (Semantic error). 
                'Message': no suitable member found.
                

However, we can be more optimistic and set all the var references in the program as dynamic. In this case, the compiler accepts a message if there is one possibility that program can behave correctly. The executable file is generated if we compile it with the everythingDynamic option:

        C:\...> stadyn /ed sample3.cs 
                

We do not need to set all the var references in the program as dynamic. It is possible to specify the dynamism of each reference with a XML file. The following is the sample3.dyn file that sets the exception reference as dynamic:

        <?xml version="1.0" encoding="utf-8"?>
        <application name="sample3">
          <namespace name="GettingStarted">
            <class name="Test">
                  <method name="Main">
                    <dynvar name="exception" />
                  </method>
                </class>
            </namespace>
        </application>
                

Finally, although we compile the following code with the everythingDynamic option, the compiler will show a static error. This means that our language processor performs static typing even in dynamic mode:

            var reference;
            if (new Random().NextDouble() < 0.5)
                reference = "String";
            else
                reference = 3;
            Console.WriteLine(reference.Message);
                

The error message obtained when compiled in dynamic mode is the following one:

        C:\...> stadyn /ed sample3.cs
        C:\...> sample4.cs(13.41): Error ErrorManagement.NoTypeHasMember (Semantic error). 
                The dynamic type '\/([Var(6)={6}=string] ,[Var(5)={5}=int])' has no valid type type with 'Message' member.
                

Implicit Generics by means of Type Reconstruction

Concrete type reconstruction is not limited to local variables. StaDyn performs a global flow-sensitive analysis of implicit var references. The result is a powerful universal polymorphism (generics) much more straightforward than the one offered by Java, C# and C++. We can create a generic Wrapper class with the following code:

    class Wrapper {
        private var attribute;

        public Wrapper(var attribute) {
            this.attribute = attribute;
        }

        public var get() {
            return attribute;
        }

        public void set(var attribute) {
            this.attribute = attribute;
        }
    }

    class Test {
        public static void Main() {
            string aString;
            int aInt;
            Wrapper wrapper = new Wrapper("Hello");
            aString = wrapper.get();
            aInt = wrapper.get(); // * Compilation error

            wrapper.set(3);
            aString = wrapper.get(); // * Compilation error
            aInt = wrapper.get();
        }
    }
                

The Wrapper class is capable of wrapping any type. Each time we call the set method, the new concrete type of the parameter is saved in the object's type. By using this mechanism the two lines indicated report compilation errors. Notice that this coding style is type safe and it is easier that the parametric polymorphism used in C++ and much more straightforward than the bounded polymorphism offered by Java and C#.

Finally, runtime performance of the code above is the same as if we had specified types explicitly. Although the source code makes use of var references, concrete types are known at compile time. The generated code uses static type information and, therefore, runtime performance is much better that the one obtained in dynamic languages.

Static and Dynamic typing in the same language

Since we do static type checking to both dynamic and static var references, it is possible to combine static and dynamic code in the same application. Let's analyze the following code:

        public static var upper(var parameter) {
            return parameter.ToUpper();
        }
        public static var getString(var parameter) {
            return parameter.ToString();
        }
                

Both methods require the parameter to implement a method, returning its value. In the second case, the method will be valid to any object. In the upper method, the parameter should be any object able to respond to the ToUpper message. This type checking system is called constraint-based typing. The next code is correct:

           string aString = "hello";
            aString=upper("hello");
            Console.WriteLine(aString);
                

In this case, the returned type is a string. What happens if the parameter could have many concrete types? Here we have an example:

          var reference;
          if (new Random().NextDouble() < 0.5)
              reference = "hello";
          else
              reference = new SystemException("A system exception");
          aString=getString(reference); // * Correct!
          Console.WriteLine(aString);
          aString=upper(reference); // * Compilation error (reference is static)
                

The reference may point to a string or to an exception. Therefore, it is type safe to invoke the getString method, but we might get an erroneous dynamic behavior if we call to the upper method. This is the reason why, if we set reference as static we get a compilation error. Otherwise, if reference is set as dynamic, the same source code will be compiled without errors. Please notice that this optimism might imply errors at runtime.

In case we set reference as a dynamic reference, could it be passed as an argument to the upper or getString methods? That is, how could optimistic (dynamic) code interoperate with pessimistic (static) one? Here we have an example:

            aString = getString(reference); // * Correct! 
            aString = upper(reference); // * Compilation error
                         // * (correct if we set parameter to dynamic)
                

The first invocation is correct regardless of the dynamism of parameter. Being optimistic or pessimistic, the argument responds to the ToString method correctly. However, it is not the same in the second scenario. By default, we obtain a compilation error, because the parameter reference is static and reference may point to an exception, causing an erroneous behavior. However, if we set the parameter of the upper method as dynamic, the compilation is correct.

We have seen how dynamic code could easily interoperate with static one. Dynamic references should satisfy the constraints inferred in every possible concrete type. Promotion of static references to dynamic ones is more flexible: static references should satisfy one constraint from the set of alternatives.

Alias Analysis for Concrete Type Evolution

The problem of determining if a storage location may be accessed in more than one way is called alias analysis. Two references are aliased if they point to the same object. It is mainly used for optimizations. We use it to know the concrete types a reference may point to. The following example uses the Wrapper class previously shown:

    class Test {
        private var testField;

        public void setField(var param) {
            this.testField = param;
        }

        public var getField() {
            return this.testField;
        }

        public static void Main() {
            var wrapper = new Wrapper("hi");
            var test = new Test();
            test.setField(wrapper);
            string s = test.getField().get(); // * Correct!

            wrapper.set(true);
            bool b = test.getField().get();   // * Correct!
            string s = test.getField().get(); // * Compilation Error
        }
    }
                

Initially the wrapper reference points to a string object. Then we create a Test object that references to the original Wrapper object. If we get the object inside the wrapper object inside the test object, we get a string object. Then we set a bool attribute to the wrapper object. Repeating the previous access to the object inside the wrapper object inside the test object, we get a bool object!

The alias analysis implemented is type-based (uses type information to decide alias), inter-procedural (makes use of inter-procedural flow information), context-sensitive (differentiates between different calls to the same method), and maybe alias (detects all the objects a reference may point to; opposite to must point to).

Alias analysis is a based tool for our type-reconstruction type system, and it is the key technique to our current stage: structural reflective type evolution (see project stages).