-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Proposal - Nested property access notation for types #10693
Comments
Just my opinions: (1) Since #1295 got accepted. I think at least this proposal should have the parameter name between the brackets as in (2) I think the syntax (3) 👍 Would rather see |
@tinganho Regarding the meaning of Without documentation, I have no idea what the following code is doing const obs = Observable.of({a: {b: {c: 1}}});
obs.pluck('a', 'b', 'c') // Observable<number>
var nested1 = Immutable.fromJS({a: {b: {c: 1}}});
nested1.getIn(['a', 'b', 'c']) // number
var nested2 = nested1.updateIn(['a', 'b', 'd'], value => value + 1); Based purely on syntactic intuition and function naming, I would expect the first to yield an object with the properties Obviously these libraries already work the way they do... |
This should be addressed by #11929 |
EDIT: type T1<T> = [(keyof T)];
type T2<T> = [(keyof T), (keyof T[(keyof T)])];
type T3<T> = [(keyof T), (keyof T[(keyof T)]), (keyof T[(keyof T)][keyof T[(keyof T)]])];
type TP<T> = T1<T> | T2<T> | T3<T>;
interface IX {
x: {
x1: void;
}
y: {
y1: void;
}
}
function select<M>(path: TP<M>): void {
//
}
select<IX>(["y", "y1"]);
|
Please file a new issue instead. |
Hey @shelakel, reviving this old thread 🙂 I ran into this issue too when trying to build a deep type safe property value extractor and ended up with the following utility method: interface TypedExtractor {
<T, K1 extends keyof T>(object: T, key1: K1): T[K1];
<T, K1 extends keyof T, K2 extends keyof T[K1]>(object: T, key1: K1, key2: K2): T[K1][K2];
<T, K1 extends keyof T, K2 extends keyof T[K1], K3 extends keyof T[K1][K2]>(object: T, key1: K1, key2: K2, key3: K3): T[K1][K2][K3];
<T, K1 extends keyof T, K2 extends keyof T[K1], K3 extends keyof T[K1][K2], K4 extends keyof T[K1][K2][K3]>(object: T, key1: K1, key2: K2, key3: K3, key4: K4): T[K1][K2][K3][K4];
<T, K1 extends keyof T, K2 extends keyof T[K1], K3 extends keyof T[K1][K2], K4 extends keyof T[K1][K2][K3], K5 extends keyof T[K1][K2][K3][K4]>(object: T, key1: K1, key2: K2, key3: K3, key4: K4, key5: K5): T[K1][K2][K3][K4][K5];
<T, K1 extends keyof T, K2 extends keyof T[K1], K3 extends keyof T[K1][K2], K4 extends keyof T[K1][K2][K3], K5 extends keyof T[K1][K2][K3][K4], K6 extends keyof T[K1][K2][K3][K4][K5]>(object: T, key1: K1, key2: K2, key3: K3, key4: K4, key5: K5, key6: K6): T[K1][K2][K3][K4][K5][K6];
<T, K1 extends keyof T, K2 extends keyof T[K1], K3 extends keyof T[K1][K2], K4 extends keyof T[K1][K2][K3], K5 extends keyof T[K1][K2][K3][K4], K6 extends keyof T[K1][K2][K3][K4][K5], K7 extends keyof T[K1][K2][K3][K4][K5][K6]>(object: T, key1: K1, key2: K2, key3: K3, key4: K4, key5: K5, key6: K6, key7: K7): T[K1][K2][K3][K4][K5][K6][K7];
<T, K1 extends keyof T, K2 extends keyof T[K1], K3 extends keyof T[K1][K2], K4 extends keyof T[K1][K2][K3], K5 extends keyof T[K1][K2][K3][K4], K6 extends keyof T[K1][K2][K3][K4][K5], K7 extends keyof T[K1][K2][K3][K4][K5][K6], K8 extends keyof T[K1][K2][K3][K4][K5][K6][K7]>(object: T, key1: K1, key2: K2, key3: K3, key4: K4, key5: K5, key6: K6, key7: K7, key8: K8): T[K1][K2][K3][K4][K5][K6][K7][K8];
<T, K1 extends keyof T, K2 extends keyof T[K1], K3 extends keyof T[K1][K2], K4 extends keyof T[K1][K2][K3], K5 extends keyof T[K1][K2][K3][K4], K6 extends keyof T[K1][K2][K3][K4][K5], K7 extends keyof T[K1][K2][K3][K4][K5][K6], K8 extends keyof T[K1][K2][K3][K4][K5][K6][K7], K9 extends keyof T[K1][K2][K3][K4][K5][K6][K7][K8]>(object: T, key1: K1, key2: K2, key3: K3, key4: K4, key5: K5, key6: K6, key7: K7, key8: K8, key9: K9): T[K1][K2][K3][K4][K5][K6][K7][K8][K9];
<T, K1 extends keyof T, K2 extends keyof T[K1], K3 extends keyof T[K1][K2], K4 extends keyof T[K1][K2][K3], K5 extends keyof T[K1][K2][K3][K4], K6 extends keyof T[K1][K2][K3][K4][K5], K7 extends keyof T[K1][K2][K3][K4][K5][K6], K8 extends keyof T[K1][K2][K3][K4][K5][K6][K7], K9 extends keyof T[K1][K2][K3][K4][K5][K6][K7][K8], K10 extends keyof T[K1][K2][K3][K4][K5][K6][K7][K8][K9]>(object: T, key1: K1, key2: K2, key3: K3, key4: K4, key5: K5, key6: K6, key7: K7, key8: K8, key9: K9, key10: K10): T[K1][K2][K3][K4][K5][K6][K7][K8][K9][K10];
}
export const X: TypedExtractor = (object: any, ...keys: string[]) =>
object && keys.every(key => ((object = object[key]) !== undefined)) ? object : undefined; Due to the TypeScript's inability to express the above constraint implicitly, I ended up writing explicit signatures for 1-10 levels of depth in the object hierarchy. It will provide type safe property access and infer return type. Maybe it will be useful to you until (or if) TypeScript supports this type of constraint implicitly or introduces support for Elvis operator. Cheers, |
This strawman proposal aims to bring type-safety to a common "nested properties access" pattern in JavaScript libraries.
A common eww-case
Consider the following snippets that use RxJS & Immutable.js: they select / update nested properties using sequences of literal property names, and are currently impossible to model in a type-safe way (making their use perillous / brittle at best):
To sleep better at night, I'd like to be able to declare something like:
(see alternative syntaxes at the bottom)
Literal index types & type property access syntax
To achieve that, we could introduce the following notations / concepts in TypeScript (
A <: B
below means any value of typeA
can be assigned to variables of typeB
):const string
andconst number
. These types sit between their respective literal types and primitive types:'foo' <: const string <: string
(any string literal type is a literal index type)1 <: const number <: number
(any number literal type is a literal index type)A <: (const string | const number)[]
) are the types of arrays literals containing any mix of string literals and number literals. They obey the same rules as the other literal types:[1, 'a']
is an literal index array type[1, string]
is not a literal index array type[const string]
is not a literal index array typeGiven a literal index type
I
(I <: (const string | const number)
):T[I]
istypeof t[i]
wheret: T
andi: I
I
is a literal string type andT
has a property which name (or constant computed property key) isi
, thenT[I]
will have that property's type.T
has an index operator that accepts keys of typeI
, thenT[I]
will be the return type of that operator. Properties are resolved before index operators ("property wins over index", TBC).T
isany
,T[I]
isany
and a warning / error is emitted if--noImplicitAny
is setNo property ${i} found on type ${T}
error is emitted.For a literal index array type
A
of lengthn
(A <: (const string | const number)[]
):T[A]
yieldstypeof t[a[0]][a[1]]...[a[n - 1]]
wheret: T
anda: A
i.e.
T[[P0, P1... Pn]] = ((T[P0])[P1])...[Pn]
(applying rules for property access with a single literal index type above, one step at a time)Where would it be useful?
To declare type signatures of lens-like APIs:
Immutable.Map.updateIn (may require some additional changes in Immutable.d.ts to get the signature useful & right, which is beyond the scope of this proposal)
Where would it be useless?
To implement those APIs: type checks will have to go through
any
at some point.This is tailored for declarations only.
More examples
Possible Extensions
There is no concept of literal symbol yet, but symbol could clearly make it in this proposal in the future:
Syntax concerns
Potential issues:
number[8]
for array of size 8), although tuples already fulfill many use-cases of fixed-size arrays.const
+ types brings lots of memories from C++ development (where const types define some sticky / recursive immutability). Only havingnumber
andstring
, which are immutable types, mitigates that risk (even if ES2100 introduces const type modifiers).Alternatives considered:
T[Props...]
: my favourite alternative (distinct-enough from spread-syntax, yet reuses existing...
token)T[...Props]
: nope (too close to spread syntax and would conflict with the awesome variadic types proposal)Other ways to address that use-case?
I'm new to TypeScript and may have overlooked some nicer
typeof
-way to do part or all of these things: please enlighten me and thanks for reading!The text was updated successfully, but these errors were encountered: