I suggest a new keyword dict
, which permits any access to non-declared properties.
// Declaring a dictionary variable
dict var some = new SomeClass();
some.x // Valid, semi-implicit 'any' type because of explicit 'dict' keyword
some.y // Valid
some.anyOtherNonPredefinedProperty // Valid
some = 3; // Fails because of incompatible type
// Declaring a dictionary interface
dict interface Bar {
}
declare var bar: Bar;
bar.x // Valid
bar.y // Valid
bar.anything // Valid
// `dict` and `extends` together
dict interface Foo extends Bar {
}
// The return type is normal SomeClass here, not dictionary one.
function someFunction() {
dict var some = new SomeClass();
/* ... */
return some;
}
For example, current DOMStringMap interface should be casted to any
to be used without errors.
document.body.dataset.someProperty = 4; // Error
document.body.dataset['someProperty'] = 4; // Alternative way, incompatible with --noImplicitAny
(<any>document.body.dataset).someProperty = 4; // Always valid
dict
here would ease this, allowing any accesses by arbitrary property names.
dict interface DOMStringMap {
}
interface HTMLElement {
dataset: DOMStringMap;
/* ... */
}
document.body.dataset.someProperty = 4; // Always valid now
Another example is the access to any property by dynamic name.
interface Foo {
x: Item[];
y: Item[];
z: Item[];
}
interface Item {
type: string; // always expected to be x, y, or z.
value: any;
}
function groupItems(...items: Item[]) {
var foo: Foo = { x: [], y: [], z: [] };
items.forEach((item) => {
foo[item.type].push(item); // This also does not work with --noImplicitAny
});
return foo;
}
Declaring foo
as dictionary allows property access with dynamic name.
/* This returns `Foo`. */
function groupItems(...items: Item[]) {
dict var foo: Foo = { x: [], y: [], z: [] };
items.forEach((item) => {
foo[item.type].push(item); // Now it will work
});
return foo;
}
Here, a dictionary variable acts as dictionary only in its function scope. Any local dictionary variables transforms to its strict version when it is returned, to keep entire type system strict.
One more example is related with ES6 computed property names.
var x = 4;
var computed = {
title: "I have a property with computed property name. Can you see it?",
[x]: 'some property value'
};
var property = computed[4]; // would it be able to be accessed with --noImplicitAny?
An object with any computed property names would implicitly have a dictionary interface, which would let var property = computed[4];
just work.
dict interface (anonymous) {
title: string;
}
A dictionary variable still has its type information, so...
- an IDE can auto-complete the names of its methods and properties.
- type checking works.
interface Foo {
x: Item[];
y: Item[];
z: Item[];
}
dict var foo: Foo = <any>{};
foo. // shows x, y, z
foo = 3; // Type checker effectively blocks this
- Interfaces are allowed to be dictionaries here, as certain interfaces would be preferred to permit arbitrary property accesses. What about classes? Should classes also be allowed to be dictionaries?
- Should we reject these:
// `some` is defined as a permanent dictionary variable.
var some: dict SomeType;
interface Bar {
someProperty: dict SomeType;
}
...and recommend defining new interface, to prevent any confusion?
dict interface DictionaryType extends SomeType {
}
If not, what syntax should be applied there?
-
x: dict T
, as in #391. This may look too similar withx: dict
. -
x: T as dict
. It may be confusing, as ES6 module syntax usesas
when importing something, giving it a new identifier. -
dict x: T
. Can this be also confusing withdict var x: T
? -
Should we allow both
dict var x
anddict let x
, or just allow onlydict x
and give it block scoping?