Re: New annotations for the Closure compiler: @struct and @dict

908 views
Skip to first unread message

Guido Tapia

unread,
Jan 20, 2013, 4:56:23 PM1/20/13
to closure-lib...@googlegroups.com
Hi All, 

I've set a bit of time to do a bit of research into @struct and @dict today and how they can help my projects.  I started by reading:


Only to realise that this has not been updated.

Is this thread the best place to learn about these annotations?  Also, suggest that someone intimate with this do a bit of documentation on the above link.

Thanks all, sorry for the 'do more docco' thread but its just a suggestion :)

Dimitris Vardoulakis

unread,
Jan 21, 2013, 2:46:19 PM1/21/13
to closure-lib...@googlegroups.com
Hi Guido,

The documentation should be updated now, thanks for noticing. There is also a wiki document about the annotations, which basically contains the same info as the above announcement.


As far as questions go, you can create a new topic on the google group for each question (just like with anything else).

Kyaw

unread,
Feb 25, 2013, 8:25:17 PM2/25/13
to closure-lib...@googlegroups.com
What if using @expose on properties instead of using @dict. @expose, at least allow IDE to check properties. Is there any performance benefit of using @dict annotation?

On Thursday, November 1, 2012 1:37:29 AM UTC+8, Dimitris Vardoulakis wrote:
We recently added two new annotations for constructors in the Closure compiler, @struct and @dict.

When you annotate a constructor C with @struct, the compiler checks that you only access the properties of C objects using dot notation. It also checks that you do not add new properties to C objects after they're created.

/** 
 * @constructor
 * @struct
 */
function Foo(x) {
  this.x = x;
}

var obj = new Foo(123);
var n1 = obj['x']; // warning
var n2 = obj.x; // OK
obj.y = "asdf"; // warning

When you annotate a constructor C with @dict, the compiler checks that you only access the properties of C objects using brackets.

/** 
 * @constructor
 * @dict
 */
function Foo(x) {
  this['x'] = x;
}

var obj = new Foo(123);
var n1 = obj.x; // warning
var n2 = obj['x']; // OK

So, structs behave like Java objects, and dicts are dictionaries. By using @struct, you know that the compiler will rename all properties safely, because you can't use bracket access. By using @dict, you know that the properties will have the same name after compilation.

You can also use @struct and @dict on object literals directly.

var s = /** @struct */ { x: 1 }, d = /** @dict */ { y: 321 };
var n1 = s[‘x’]; // warning
var n2 = d.y; // warning

This is how the new annotations work in a nutshell. Read on to find out all the intricacies of the new annotations.

More details

Inheritance maintains the annotations

Consider the following example.

/** @constructor */
function Foo(x) {
  this.x = x;
}

/** 
 * @constructor
 * @struct
 * @extends {Foo}
 */
function Bar(x, y) {
  this.x = x;
  this.y = y;
}

/** @param{Foo} obj */
function getx(obj) { return obj[‘x’]; }
var z = getx(new Bar(123, 456)); // no warning

In this example, Bar inherits from Foo and is also annotated with @struct. But then, a function that takes Foos can use bracket access for Bar objects, because Bar is a subtype of Foo. For this reason, the compiler does not allow a struct or dict class to extend an unannotated class. Also, when Bar extends Foo and Foo is annotated with @struct or @dict, Bar automatically inherits the annotation.

Annotating the prototype

Since annotated constructors can’t inherit from unannotated ones, when you annotate a constructor with @struct, you must also annotate the prototype.

One way to do this is to create a constructor for the prototype and annotate that constructor.

/** 
 * @constructor
 * @struct
 */
function FooProto() {
  this.identity = function(x) { return x; };
  this.add1 = function(x) { return x+1; };
}

/** 
 * @constructor
 * @struct
 */
function Foo(x) {
  this.x = x;
}

Foo.prototype = new FooProto();
Foo.prototype.sub1 = function(x) { return x-1; }; // warning

This is verbose because you create FooProto and only use it once. Alternatively, you can use an annotated object literal.

/** 
 * @constructor
 * @struct
 */
function Foo(x) {
  this.x = x;
}

Foo.prototype = /** @struct */ {
  id: function(x) { return x; },
  add1: function(x) { return x+1; }
};

Foo.prototype.sub1 = function(x) { return x-1; }; // warning

Subtyping and unsoundness

There are a few cases where the compiler cannot enforce the properties of structs and dicts. Fixing those cases would require breaking changes to the type system, so we decided to live with some missed checks. 
First, structs and dicts are subtypes of Object. The compiler cannot enforce the struct/dict properties for the Object type.

/** 
 * @constructor
 * @dict
 */
function Foo(x) {
  this[‘x’] = x;
}

/** @param{Object} obj */
function fun1(obj) { return obj.toString(); }

fun1(new Foo(123)); // dict accessed with dot but no warning

Second, if a struct or dict implements an interface, it’s a subtype of that interface. The compiler cannot enforce the struct/dict properties for the interface type.

/** @interface */
function Foo() {}

/**
 * @constructor
 * @struct
 * @implements {Foo}
 */
function Bar() { this.x = 123; }

var n = /** @type{Foo} */(new Bar())[‘x’];

By casting to Foo, the compiler doesn't warn about the illegal bracket access.

Last, structs and dicts can be used in places that expect a record type, like any other class. The compiler cannot enforce the struct/dict properties for the record type.

/** 
 * @constructor
 * @struct
 */
function Foo() { this.x = 123; }

/** @param{{x: number}} rec */
function fun1(rec) { return rec['x']; }

fun1(new Foo()); // struct accessed with brackets but no warning

John Lenz

unread,
Feb 25, 2013, 8:42:44 PM2/25/13
to closure-library
@dict is intended for objects that intended to be used as maps (or
dictionaries). Primarily, it helps with the separation expect
edbetween internal and external properties in advanced mode.
> --
>
> ---
> You received this message because you are subscribed to the Google Groups
> "Closure Library Discuss" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to closure-library-d...@googlegroups.com.
> For more options, visit https://groups.google.com/groups/opt_out.
>
>

Dimitris Vardoulakis

unread,
Apr 9, 2013, 2:02:00 AM4/9/13
to closure-lib...@googlegroups.com, ro...@dbservice.com
The following code compiles without warnings at http://closure-compiler.appspot.com/home

/**
 * @constructor
 * @struct
 */
var A = function() {};

A.prototype.a1 = function() {};
A.prototype.a2 = function() {};
A.prototype.a3 = function() {};

/**
 * @constructor
 * @struct
 * @extends {A}
 */
var B = function() {};

B.prototype = new A();

B.prototype.b1 = function() {};
B.prototype.b2 = function() {};


The fact that Object.create and goog.inherits don't work is probably a bug.


On Friday, April 5, 2013 4:12:05 AM UTC-7, ro...@dbservice.com wrote:
Could you please extend the documentation for inheritance? In the example below, the @extends annotation prevents you from adding new functions to the subclass prototype, even though adding functions to the base class prototype is ok.
 
/**
@constructor
@struct
*/
A = function() {}

A.prototype.a1 = function() {};  
A.prototype.a2 = function() {};  
A.prototype.a3 = function() {};  

/**
@constructor
@struct
@extends {A}
*/
B = function() {};

// B.prototype = Object.create(A.prototype); // JSC_CONFLICTING_EXTENDED_TYPE
// B.prototype = new A();                    // JSC_ILLEGAL_PROPERTY_CREATION
// goog.inherits(B, A);                      // JSC_ILLEGAL_PROPERTY_CREATION

B.prototype.b1 = function() {}; // JSC_ILLEGAL_PROPERTY_CREATION
B.prototype.b2 = function() {}; // JSC_ILLEGAL_PROPERTY_CREATION

b = new B();

Robin Carnecky

unread,
Apr 9, 2013, 2:35:05 AM4/9/13
to closure-lib...@googlegroups.com, Dimitris Vardoulakis
One difference between your code and mine is that you use the var keyword for the functions A and B. If you do not use the keyword at all, or if you put a "var A, B;" at the top of my code, you'll get the warnings I described. In fact, it is enough to put a var keyword in front of B to get rid of the warnings.

I can't tell whether these are bugs in closure or working as intended - if those are bugs, would you mind reporting them? By the way, the goog.inherits call works with the var keywords, while the Object.create call still produces warnings.

From: Dimitris Vardoulakis [mailto:dim...@google.com]
To: closure-lib...@googlegroups.com
Cc: ro...@dbservice.com
Sent: Tue, 09 Apr 2013 08:02:00 +0100
Subject: Re: New annotations for the Closure compiler: @struct and @dict

Dimitris Vardoulakis

unread,
Apr 9, 2013, 2:37:31 AM4/9/13
to Robin Carnecky, closure-lib...@googlegroups.com
You must use var otherwise Closure complains that the variables aren't declared. This is independent of @struct and @dict.
--
Dimitris

Robin Carnecky

unread,
Apr 9, 2013, 2:55:37 AM4/9/13
to closure-lib...@googlegroups.com, Dimitris Vardoulakis
Well that's not true. Closure happily compiles constructors with or without the var keyword (independent of whether the keyword is on the same line or not). Only difference is that the function is not renamed if there is no var keyword.

If it is indeed a requirement that constructors are declared with a var keyword and that the var keyword appears in the same statement as the constructor definition, I'd suggest adding a warning for that.

The following code works fine and compiles without warnings:

/**
@constructor
*/
A = function(s) {this.str = s}
A.prototype.foo = function() {alert(this.str);}

a = new A("works");
a.foo();



From: Dimitris Vardoulakis [mailto:dim...@google.com]
To: Robin Carnecky [mailto:ro...@dbservice.com]
Cc: closure-lib...@googlegroups.com
Sent: Tue, 09 Apr 2013 08:37:31 +0100

Dimitris Vardoulakis

unread,
Apr 9, 2013, 2:03:06 PM4/9/13
to Robin Carnecky, closure-lib...@googlegroups.com
We tried different options, that's why we get different results. To see the warning about undeclared variables, you need to enable the checkVars check. (For some reason I thought the check was on by default, but I guess not.)

java -jar build/compiler.jar --jscomp_warning checkVars --js yourJsCode.js

If you're using the compiler at any level other than WHITESPACE_ONLY, it's good practice to enable that check, and also --jscomp_warning checkTypes.

The compiler doesn't rename global variables because that may change the meaning of the program (eg, can't rename window).

In any case, global variables and @struct/@dict are separate issues. If you'd like to file specific bugs about the problems you're seeing, do it here:
and include exact steps to reproduce the bug.

--
Dimitris

Tom Payne

unread,
Dec 5, 2013, 7:00:52 PM12/5/13
to closure-lib...@googlegroups.com, closure-comp...@googlegroups.com, Eric Lemoine
Digging up an old thread here. Cross-post to both Closure Compiler and Closure Library as it concerns both. The original thread is here:
  https://groups.google.com/d/msg/closure-compiler-discuss/i6PT4qKiyCQ/omzOTIGLw_YJ

I recently tried to use the @struct and @dict annotations to the codebase that I'm working on in an effort to catch properties being initialised outside the constructor [1]. However, the "if a class is a @struct, then its parent must also be a @struct" immediately limited what I could do. A lot of our classes inherit from the Closure Library's goog.events.EventType and goog.Disposable classes, which do not have @struct or @dict annotations. So, even though we want our classes to be well-behaved, the Closure Compiler cannot help us enforce this.

So, a couple o' questions:
- Is there any progress towards using @struct and @dict more widely in the Closure Library? Right now it's used in a few classes, but not many.
- Given the strictness of the "@structs classes parent classes must also be @structs" requirement - which is likely to make making existing codebases compatible a Herculean task - is there any progress in a more flexible approach from the compiler which would allow wider use of @struct?

Cheers,
Tom



--

Dimitris Vardoulakis

unread,
Dec 5, 2013, 7:07:38 PM12/5/13
to closure-comp...@googlegroups.com, closure-lib...@googlegroups.com, Eric Lemoine
On Thu, Dec 5, 2013 at 4:00 PM, Tom Payne <t...@tompayne.org> wrote:
Digging up an old thread here. Cross-post to both Closure Compiler and Closure Library as it concerns both. The original thread is here:
  https://groups.google.com/d/msg/closure-compiler-discuss/i6PT4qKiyCQ/omzOTIGLw_YJ

I recently tried to use the @struct and @dict annotations to the codebase that I'm working on in an effort to catch properties being initialised outside the constructor [1]. However, the "if a class is a @struct, then its parent must also be a @struct" immediately limited what I could do. A lot of our classes inherit from the Closure Library's goog.events.EventType and goog.Disposable classes, which do not have @struct or @dict annotations. So, even though we want our classes to be well-behaved, the Closure Compiler cannot help us enforce this.

So, a couple o' questions:
- Is there any progress towards using @struct and @dict more widely in the Closure Library? Right now it's used in a few classes, but not many.

People have been adding these, but it's a slow process.
 
- Given the strictness of the "@structs classes parent classes must also be @structs" requirement - which is likely to make making existing codebases compatible a Herculean task - is there any progress in a more flexible approach from the compiler which would allow wider use of @struct?

You can set the level of this diagnostic group to off, to avoid the warning
 

--
 
---
You received this message because you are subscribed to the Google Groups "Closure Compiler Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to closure-compiler-d...@googlegroups.com.

For more options, visit https://groups.google.com/groups/opt_out.



--
Dimitris

Demetrios Papadopoulos

unread,
Dec 5, 2013, 9:15:32 PM12/5/13
to closure-lib...@googlegroups.com, closure-comp...@googlegroups.com, Eric Lemoine
In the meantime you can add "@suppress {checkStructDictInheritance}"
right after "@struct". This will allow any class to be a struct,
regardless of its superclass.

On Thu, Dec 5, 2013 at 4:00 PM, Tom Payne <t...@tompayne.org> wrote:

Jaron Heinz

unread,
Oct 29, 2014, 11:54:32 PM10/29/14
to closure-lib...@googlegroups.com
The @struct annotation is a pretty nice feature. Would it make sense to add a flag to make all classes @struct by default? I think 90% of the time that's what you want, and the other 10% you can manually somehow unmark them as struct..

Peter StJ

unread,
Oct 30, 2014, 4:52:16 AM10/30/14
to closure-lib...@googlegroups.com
From what I thinkg I figured out - using goog.defineClass always assumes struct. Unfortunately it is not always a straight-forward to use it (i.e. some edge cases when you need to refer to a static property on the class but the statics are not there yet - in the regular (or scoped) syntax it works but not with defineClass because in source mode the reference gets broken. Still looks much more nicer and has less to type.
Reply all
Reply to author
Forward
0 new messages