Skip to content

Instantly share code, notes, and snippets.

@saschanaz
Last active August 29, 2015 14:04
Show Gist options
  • Save saschanaz/21a1f63de9c65b65f965 to your computer and use it in GitHub Desktop.
Save saschanaz/21a1f63de9c65b65f965 to your computer and use it in GitHub Desktop.
Design proposal: Dictionary

I suggest a new keyword dict, which permits any access to non-declared properties.

Syntax

// 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;
}

Use Cases

DOMStringMap

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

Property access by dynamic name

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.

Computed property names

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;
}

Why dict instead of any?

A dictionary variable still has its type information, so...

  1. an IDE can auto-complete the names of its methods and properties.
  2. 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

Notes

Questions

  1. 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?
  2. 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?

  1. x: dict T, as in #391. This may look too similar with x: dict.

  2. x: T as dict. It may be confusing, as ES6 module syntax uses as when importing something, giving it a new identifier.

  3. dict x: T. Can this be also confusing with dict var x: T?

  4. Should we allow both dict var x and dict let x, or just allow only dict x and give it block scoping?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment