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

Splitting computed and named properties in an interface into two categories #8597

Closed
Thomas-P opened this issue May 13, 2016 · 10 comments
Closed
Labels
Question An issue which isn't directly actionable in code Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@Thomas-P
Copy link

Hi,

I have a question about computed properties. I have an interface that can have mutliple computed properties which can have a string as value and some specified properties that should have a number as their value. So my interface looks like this.

interface IComputedTest{
    [name: string]: string
    numberProperty: number;
}

On building the interfaces like this, I thought that they could work with a specification priority. So computed priority is very low and a numberProperty than overwrite the rule for the computed property. But since I realized that it would not work like this for now, my question is how can I solve the problem, that I have specific keys with other types and computed keys with there own type?

Is there a way around them?

@mhegazy
Copy link
Contributor

mhegazy commented May 13, 2016

When you index into a value of this type using a string, the compiler does not know if its a computed property or a named property. e.g.:

var x: string = getName();

var test: IComputedTest;

var y = test[x] // what is the type of `y`?

The index signature [name: string]: string is a constraint on the type IComptutedTest. however, in this declaration, the name property violates this constraint. the only thing we can do in a type safe way is to define the index signature to return number|string. if you do not care about type safety for this specific type, you can always define the index signature to return any.

@mhegazy mhegazy added Question An issue which isn't directly actionable in code Working as Intended The behavior described is the intended behavior; this is not a bug labels May 13, 2016
@Thomas-P
Copy link
Author

Hi,

therefor i had an assertion pattern, that checks if the right value is set. I mean, that it could be possible to have static key name that can have a specific type?

For example this works fine for me:

interface IComputetTest {
    [name: string]: string|number;
    numberProperty: number;
}

let x:IComputetTest = {
    numberProperty: 1 
};

let y = function():string {
    return 'numberProperty'
}
x[y()] = '2a'; // can't check, so string|number fallback (better string only but ok)
x.numberProperty = 1; // only number allowed

This example work quite well, but I think I have to accept, that strings and numbers must be allowed then.

Ok, I played a litte bit with interfaces and think I get a good solution:

interface IExpression {
    [key:string]: IExpression|Array<IExpression>|Array<string|number|boolean>|string|number|boolean;
    $in?: Array<string|number|boolean>;
    $or?: Array<IExpression>;
    $and?: Array<IExpression>;
    $lt?: number;
}


var testIFind: IExpression;
testIFind = {
    $lt: 1,
    a: {
        $lt: 4
    }
}

// https://docs.mongodb.com/manual/reference/operator/query/or/
testIFind = { $or: [ { quantity: { $lt: 20 } }, { price: 10 } ] };

// https://docs.mongodb.com/manual/reference/operator/query/and/
testIFind = { $and: [ { price: { $ne: 1.99 } }, { price: { $exists: true } } ] };
testIFind = {
    $and : [
        { $or : [ { price : 0.99 }, { price : 1.99 } ] },
        { $or : [ { sale : true }, { qty : { $lt : 20 } } ] }
    ]
};

That work nearly as well as I intended and it is fine to create objects and allows me to check if the static object definition is correct by compiler. But is there a way around writing

string|number|boolean

again and again?

@malibuzios
Copy link

malibuzios commented May 13, 2016

I posted an experimental idea for 'rest' index signatures. The particular comment linked described a 'typed' variant that may help here:

interface IComputedTest {
    numberProperty: number;
    [name: ...string]: string;
}

There are other ideas included there, like string literal types as keys and a hypothetical Error type that I haven't really managed to figure out from a more 'theoretical' perspective. These additional features are not really necessary for this concept, despite the fact that originally they were major motivators.

It is possible to make a more 'basic' proposal, which would only include [key: ...string] and [key: ...number] (not sure about [key: ...Symbol] as it isn't currently supported, but maybe in the future), as a small extension for the more 'conventional' uses of index signatures today.

@Thomas-P
Copy link
Author

In short I forgot to mention that the compiler fallback to computed if you use brackets. The compiler should so only check static keys that could have other types than computed or dynamic properties. At now it works if the dynamic property has also the type of the static key and it will check as well.

@Thomas-P
Copy link
Author

@malibuzios This signature idea is much more I want. But I subscribed it.

@mhegazy
Copy link
Contributor

mhegazy commented May 13, 2016

I do not think there is a way to make this really work for the same type. there is nothing that the compiler can do statically to grantee a string value is not the name of one of the known properties..

interface IComputedTest {
    numberProperty: number;
    [name: ...string]: string;
}

var test: IComputedTest ;

var s = "numberProperty";

test[s];  // it is a number at runtime, regardless of the index signature you defined.

@mhegazy
Copy link
Contributor

mhegazy commented May 13, 2016

@Thomas-P the work around is to use two types. e.g.:

type IComputetTest = {numberProperty: number} & {[name: string]: string;};


var x: IComputetTest;

x.numberProperty; // number
x["other"]; // string

@mhegazy mhegazy closed this as completed May 13, 2016
@malibuzios
Copy link

malibuzios commented May 14, 2016

@mhegazy

I couldn't follow the explanation. The intersection type workaround does work, though isn't very elegant or intuitive to understand (even for someone deeply familiar with the language, there is still a significant doubt it would error and not clear what it should do exactly). The first example had a variable with a string type used as an index into a variable having the interface type, however the second was a completely different example.

Using the intersection workaround with the same exact scenario as the first example doesn't really help:

type IComputetTest = {numberProperty: number} & {[name: string]: string;};
declare var x: IComputetTest;

x.numberProperty; // number, with both approaches

let s = "numberProperty";
let y = x[s]; // 'y' gets type string -> the intersection type doesn't really help here.

(perhaps something like #1295 could help here to correctly resolve the x[s] to number? but I'm not sure how is that even related to the subject?)

@malibuzios
Copy link

malibuzios commented May 14, 2016

[tested on 1.9.0-dev.20160514]

Thinking about this further, despite the fact that this intersection type:

{ [key: string]: string } & { prop: number }

doesn't currently error, it isn't really logically consistent.

  1. The first component of the intersection says: "when indexing into a string key, the property type would be string".
  2. The second says "when dereferencing or assigning (either using the . or [] operators) the key 'prop', the property type would be number".

Combining the two would mean that key 'prop' is both a string and a number, which is inconsistent (intersection types apply the and, not or operation), or at least shouldn't yield a usable type (the intersected property type is string & number in the next example).

This simpler example demonstrates this more clearly:

let a: { prop: string } & { prop: number };

a.prop = 1; // error: type 'number' is not assignable to 'string & number'
a.prop = "hi"; // error: type 'string' is not assignable to 'string & number'

@Thomas-P
Copy link
Author

Hi @malibuzios and @mhegazy,

the Issue is already closed but I have found out that in one case TypeScript already works as I expected, but a also little different:

interface IWithOther {
    [key: string]: Object;
    property: number;
}

Link

So I got an any without using any and now I don't want you to change this behavior, because then my little hack for the type description files won't work. But I will mention that I use TypeScript for the static type checking and that is awesome. JavaScript allows us to add some properties to an object and that is something computed properties will also do. We have two things that TypeScript do as it expect. Check static type and check dynamic/computed types. So why do I have to say all computed types must also have all the static property types? That is something that I not expected, because these are two worlds.

@malibuzios I think for your example it awaits a property that have a string and a number type. But by intersecting there should be a used or or the last one. Looks like a bug, but should be tested. It reminds me to this one:

interface IVal {
    string;
    number;
}

let a: IVal;
a= 1;
a= '1';

It has the same effect. Instead of using the or operator it awaits a value that I could not generate. So there are some errors.

@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
Question An issue which isn't directly actionable in code Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

3 participants