Support for Javascript prototypes and QML directory imports

Published on jeu 10 juillet 2014 in Nepomuk, (Comments)

Here is a new status report of my work on the KDevelop QML/JS language support plugin. This week, I have fixed several small bugs and implemented two features: support for function prototypes (a Javascript-only feature), and support for directory imports in QML.

Function prototypes

I think that this was the feature the most difficult to implement of all the GSoC project. Moreover, support for function prototypes and Javascript object is not yet complete, and I doubt that the plugin will be able to support the whole Javascript prototype-oriented programming. The reason is that Javascript is a very dynamic language while KDevelop is very static. I'll show you examples of code snippets that would require, in fact, a complete Javascript interpreter and type tracer for them to be correctly handled in KDevelop.

Currently, the feature starts to be usable but is still minimal, mainly because my knowledge of Javascript prototypes is also minimal. I've implement only what I've understood and what was possible without adding thousands of lines of code to the QML/JS plugin. The goal of my GSoC is to have the best QML support possible, with decent Javascript support.

The main idea of the prototype support is to associate to every function a "prototype". Here, the word does not have the exact same meaning as in the Javascript literature. My definition of the prototype of a function is "the context of the this identifier in this function". Several functions, if part of the same class, can therefore share the same prototype, because "this" will have the same value in all of them. Moreover, there is no distinction between the prototype of a function and the objects created when calling new Function().

This and its members

In the above screenshot, a function SayHello is declared. As it is a function declaration (with the function keyword), a new prototype is created and assigned to it. In the function body, this becomes available and can be modified (here, I simply add a key in it). The second function is a function expression, that gets assigned to a method of the prototype. When such an assignation is detected, the QML/JS plugin sets the prototype of the function expression to the prototype to which it is assigned. Here, SayHello.geet() and SayHello() share the same prototype. You can see that this.name points to the same declaration in both functions.

Instantiating a function

This second screenshot shows how a function can be instantiated (instantiating a function? Javascript is very weird sometimes...). A variable named "greeter" is created, and its type is set to a StructureType. A StructureType represents a class instance in KDevelop, and can be used here to say that "greeter" is an instance of "SayHello".

Now that "greeter" knows that it is an instance of "SayHello", and because I've added a bit of magick in how functions are handled when they are instantiated, the content of the prototype of "SayHello" becomes visible in greeter:

Attributes of a class instance

This works fairly well and already allows complex libraries like impress.js to be somewhat usable, but there are still plenty of features missing, some of them being tightly bound to the dynamic nature of Javascript. For instance, "class inheritance" (or emulating it) is very complex and requires assigning a value to the prototype of a function. This could be supported, but ClassA.prototype = ClassB is not the recommended way of doing inheritance in Javascript. I've seen an example looking like this: ClassA.prototype = Object.createObject(ClassB.prototype);. The call to createObject hides the type of ClassB from the type deduction system of the plugin and greatly complexifies things.

Even more advanced constructs, like assigning a function to the constructor property of an object, or playing with its __proto__ attribute, is nearly impossible to handle properly. I don't know if these kinds of constructs are widely used, but one single use at the base of a well-known Javascript framework can make the QML/JS plugin useless for the whole framework.

The last point is that the QML/JS plugin makes no distinction between the prototype of a function and the objects intantiated from it:

1
2
3
4
5
6
7
8
function MyClass() {
}

var a = new MyClass();
var b = new MyClass();

a.foo = "bar";
// now b also has a "bar" attribute

Solving this would be a bit complicated, and I think that such a behavior may even be desirable: imagine that QML/JS has missed the declaration of a member of "MyClass", because it was hidden in a function, used an unsupported construct, or anything else. The first time the user (or the library!) will try to use this missing member, it will be declared on the fly. This way, the user may even not notice that the real declaration of the member has been missed.

QML directory imports

Back to the nice world of staticly-typed and C++-ish QML :-). QML-related features are less challenging to implement, but QML is a very rich language that has many constructs that need to be handled. One example is QML imports, which exist in four flavors!

  • Standard module imports: import QtQuick 2.2 and import QtQuick 2.2 as QQ.
  • Automatic import of all the QML files in the current directory, so that a MyComponent.qml file can be instantiated by using the MyComponent component. No import statement needed.
  • Import of whole directories: import "../mydir"and import "../mydir" as MyDir. All the QML files in the imported directory are made available to the current QML files through their associated component names.
  • Import of Javascript files: import "../../js/module/myfile.js" as JSFile. Here, the "as" is mandatory. All the top-level declarations of the Javascript file (functions, variables, objects, etc) become available in a namespace named "JSFile" in my example.

The first kind of import statement was supported for a long time. Support for the second type of imports has been added recently, and the last two types of import statements have been added today:

Importing a Javascript file

Importing directories is also possible, but I did not have any interesting directory that deserved a screenshot. As always, every feature is unit-tested, and you can see the directory import feature at work in its unit test.

« Function call-tips and automatic declaration of object members   ECMAScript 6 support in KDevelop »