Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suggestion: design-time only propertiesOf operator #11813

Closed
remojansen opened this issue Oct 24, 2016 · 3 comments
Closed

Suggestion: design-time only propertiesOf operator #11813

remojansen opened this issue Oct 24, 2016 · 3 comments
Labels
Duplicate An existing issue was already created

Comments

@remojansen
Copy link
Contributor

remojansen commented Oct 24, 2016

Hi,

I'm creating this issue to suggest a feature which is kind of related with type reflection. I don't know if it is possible to implement or if it is something that aligns with the TypeScript project goals but I though that it would be worth to share it...

The problem

I'm trying to think about ways to improve the support for immutable.js or similar libraries.

The current .d.ts file uses a generics and two type arguments (key and value) to declare aMap:

import Immutable = require('immutable');
var map1: Immutable.Map<string, number>;
var map = {a: 1, b: 2, c: 3};
map1 = Immutable.Map(map );
var map2 = map1.set('b', 50);
map1.get('b'); // 2
map2.get('b'); // 50

This is a problem because in some cases not all the values will be of the same type:

var map = {a: 1, b: "test", c: 3};

However, this is not a major problem because we can use union types:

var map1: Immutable.Map<string, (string|number)>;
map1 = Immutable.Map(map);

The second problem is more serious:

map2.get('d'); // No error! We need something!

The compiler is not able to validate that we have used the correct string literal.

The not so good solution

After trying a few things I decided I decided that it would be much better to do something like the following:

interface ImmutableMap<TMap, TPropertiesOfMap> {
    set<TProp>(prop: TPropertiesOfMap, val: TProp): ImmutableMap<TMap, TPropertiesOfMap>;
    get<TProp>(prop: TPropertiesOfMap): TProp;
    toJS(): TMap;
}

interface Immutable {
    Map<TMap, TPropertiesOfMap>(map: TMap): ImmutableMap<TMap, TPropertiesOfMap>;
}

declare var Immutable: Immutable;

interface MyMap { 
    a: number;
    b: string;
    c: number;
}

type PropertiesOfMyMap = "a" | "b" | "c";

var map = { a: 1, b: "something", c: 3 };

var map1 = Immutable.Map<MyMap, PropertiesOfMyMap>(map);
var map2 = map1.set<number>("a", 50);

let num1 = map1.get<number>("a"); // 1
let num2 = map2.get<number>("a"); // 50
let string1 = map2.get<string>("b"); // "something"
let num3 = map2.get<number>("d"); // Error (as expected)

let js = map2.toJS();
js.a; // 50
js.b; // "something"
js.d; // Error (as expected)

The main problem I see with this is having to manually create the following type:

type PropertiesOfMyMap = "a" | "b" | "c";

I believe that this is a potential failure point. A place in which we may type something incorrectly or forget one of the properties.

The suggested solution

I was wondering if it would be possible to implement a design time only type operator. I have called this operator propertiesOf:

interface MyMap { 
    a: 1;
    b: 2;
    c: 3;
}

type PropertiesOfMyMap = propertiesOf MyMap; // "a" | "b" | "c"

If this operator was applicable to generic types we could re-implement the immutable example as follows:

interface ImmutableMap<TMap> {
    set<TProp>(prop: propertiesOf TMap, val: TProp): ImmutableMap<TMap>;
    get<TProp>(prop: propertiesOf TMap): TProp;
    toJS(): TMap;
}

interface Immutable {
    Map<TMap>(map: TMap): ImmutableMap<TMap>;
}

declare var Immutable: Immutable;

interface MyMap { 
    a: number;
    b: string;
    c: number;
}

var map = { a: 1, b: "something", c: 3 };

var map1 = Immutable.Map<MyMap>(map);
var map2 = map1.set<number>("a", 50);

let num1 = map1.get<number>("a"); // 1
let num2 = map2.get<number>("a"); // 50
let string1 = map2.get<string>("b"); // "something"
let num3 = map2.get<number>("d"); // Should throw an Error

let js = map2.toJS();
js.a; // 50
js.b; // "something"
js.d; // Error (as expected)

I'm not sure but this operator could maybe also prevent index property access errors?

var map = { a: 1, b: 2, c: 3 };
map["a"]; // 1
map["d"]; // Not Error! Could propertiesOf help here?

Thanks in advance for taking the time to review this 👍

@yortus
Copy link
Contributor

yortus commented Oct 24, 2016

Is this equivalent to the keysof type operator in #10425?

@remojansen
Copy link
Contributor Author

Yes! awesome I didn't know that this was almost merged! 😄

@RyanCavanaugh RyanCavanaugh added the Duplicate An existing issue was already created label Oct 24, 2016
@RyanCavanaugh
Copy link
Member

Thanks @yortus !

@microsoft microsoft locked and limited conversation to collaborators Jun 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests

3 participants