-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Constraint Types Proposal #13257
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Comments
Why not use a ternary operator? Instead of: let other: (value is number: string | value is string: boolean) This: let other: (value is number ? string : value is string ? boolean) Looks less confusing for me. |
this is what they call dependent types, isnt it? if so,
|
@Aleksey-Bykov It's not quite dependent types, because values cannot be lifted to types, nor can types be lifted to values. This proposal only allows proper type -> type functions (we already have value -> value functions), but it's missing the other two cases. And no,
interface ArrayConstructor {
isArray(value: any): value is any[];
} And function staticAssert<T>() {}
declare const value: any
if (Array.isArray(value)) {
staticAssert<Is<typeof value, any[]>>()
} else {
staticAssert<NotA<typeof value, any[]>>()
} |
The reason I chose not to use a ternary is because of two reasons:
|
i suggest you list type overloads one per line (rather than all in the samr
`|` separated list), it's cleaner, proven to work, doesn't need 3nary
operator
…On Jan 8, 2017 8:03 PM, "Isiah Meadows" ***@***.***> wrote:
@asfernandes <https://github.com/asfernandes>
The reason I chose not to use a ternary is because of two reasons:
1.
It visually conflicts with nullable types, and is also much harder to
parse (you have to parse the whole expression before you know if the right
side is a nullable type):
type Foo = (value is number ? string : boolean)type Foo = (value is number? ? string : boolean)
2.
Types may have more than two different conditions (e.g. with
overloaded types), and the ternary operator doesn't make for very readable
code in this case.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#13257 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AA5PzZI0WDoZcUpIVQ6w65Z8dCdTSOecks5rQYdlgaJpZM4LZObU>
.
|
@Aleksey-Bykov TypeScript only cares about newlines for ASI, and there's no other precedent for newline significance. Additionally, I intentionally phrased the constraint types as a union of constraints, so they could be similarly narrowed with control flow (just as current union types can be), and that's why I used the As for why I chose a union instead of separate type overloads, here's why:
I did in fact already consider the idea of type overloads, and initially tried that route before posting this. The above issues, especially the first three, are why I elected to go a different route instead. |
@isiahmeadows, your proposal is what I would like to see in TypeScript. I would like to use a |
However, I wanted to move in the direction of type decomposition via switch-types type CoerceToValue<T> = switch(T) {
case<V> Thenable<V>: V;
default: T;
} or even type CoerceToValue<T> = switch(T) {
case<V> Thenable<V>: CoerceToValue<V>;
default: T;
} |
@Artazor That might work, too, but I have my reservations in terms of expressiveness. How would you do something equivalent to my // Using my proposal's syntax
type Super<T> = (U in T extends U: U);
interface Promise<T extends (T extends Thenable<any>: * | T: T)> {} |
What about |
@bobappleyard Can't use it because it's already a valid return type (specifically a bottom type, a subtype of every type). function error(): never {
throw new Error()
} I need an impossibility type, where even merely matching it is contradictory. |
Note to all: I updated my proposal some to aid in readability. |
I'd like to solve something like #12424. I just want to check, if I have understood this proposal correctly. Say I have this example: // some nested data
interface Data {
foo: string;
bar: {
baz: string;
};
}
const data: Data = {
foo: 'abc',
bar: {
baz: 'edf'
}
};
// a generic box to wrap data
type Box<T> = {
value: T;
};
interface BoxedData {
foo: Box<string>;
bar: Box<{
baz: Box<string>;
}>;
}
const boxedData: BoxedData = {
foo: {
value: 'abc'
},
bar: {
value: {
baz: {
value: 'edf'
}
}
}
}; I'd like to express // should box everything and if T is an object,
// - it should box its properties
// - as well as itself in a box
// - and arbitrary deep
type Boxing<T> = [
T is object: [P in keyof T]: Boxing<T[P]> & Box<T>,
T: Box<T>,
]
type BoxedData = Boxing<Data> And how would it look like with arrays? E.g. interface Data {
foo: string;
bar: {
baz: string;
};
foos: Array<{ foo: string }>;
}
// arrays should be boxed, too and all of the items inside of the array
type Boxing<T> = [
T is object: [P in keyof T]: Boxing<T[P]> & Box<T>,
T is Array<ItemT>: Boxing<Array<ItemT>> & Box<T>,
T: Box<T>,
]
type BoxedData = Boxing<Data>
const boxedData: BoxedData = {
foo: {
value: 'abc'
},
bar: {
value: {
baz: {
value: 'edf'
}
}
},
foos: {
value: [
{
foo: { value: '...' }
}
]
}
}; My use case are forms which can take any data (nested and with arrays) as an input and have a data structure which matches the input data structure, but with additional fields (like |
@donaldpipowitch Your // arrays should be boxed, too and all of the items inside of the array
type Boxing<T> = [
T extends object: {[P in keyof T]: Boxing<T[P]> & Box<T>},
U in T extends U[]: Boxing<U[]> & Box<T>,
T: Box<T>,
] Note a few changes:
|
@donaldpipowitch Updated with my newest changes: type Boxing<T> = [
case T extends object: {[P in keyof T]: Boxing<T[P]> & Box<T>},
case U in T extends U[]: Boxing<U[]> & Box<T>,
default: Box<T>,
] I added keywords to make it a little more readable and obvious at the cost of a little extra verbosity. |
Your first set of examples are still using the old syntax
…On 27 Mar 2017 16:36, "Isiah Meadows" ***@***.***> wrote:
@donaldpipowitch <https://github.com/donaldpipowitch> Updated with my
newest changes:
type Boxing<T> = [
case T extends object: {[P in keyof T]: Boxing<T[P]> & Box<T>},
case U in T extends U[]: Boxing<U[]> & Box<T>,
default: Box<T>,
]
I added keywords to make it a little more readable and obvious at the cost
of a little extra verbosity.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#13257 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AACMHAYXqhpJSdWGZnoiPmGHk2z7xIVGks5rp9d7gaJpZM4LZObU>
.
|
@bobappleyard Thanks for the catch! Should be fixed now. |
@isiahmeadows Thank you. This looks really useful for our use case. I hope this will be implemented someday ❤ |
that little extra verbosity finally makes it readable to a layman, such as myself. :P |
@MeirionHughes Welcome |
Looks like a great idea with a lot of immediate uses :) Any idea when this might hit production? :) |
Same here, mobx-state-tree would benefit highly from this feature |
@isiahmeadows I want to write types for Telegram API Update type. Are this right types? type XMessage = { message: Message }
type XEditedMessage = { edited_message: Message }
type XChannelPost = { channel_post: Message }
type XEditedChannelPost = { edited_channel_post: Message }
type XInlineQuery = { inline_query: InlineQuery }
type XChosenInlineResult = { chosen_inline_result: ChosenInlineResult }
type XCallbackQuery = { callback_query: CallbackQuery }
type Update<X> = [
case X is XMessage: X,
case X is XEditedMessage: X,
case X is XChannelPost: X,
case X is XEditedChannelPost: X,
case X is XInlineQuery: X,
case X is XChosenInlineResult: X,
case X is XCallbackQuery: X,
default: throw
]
const value = getUpdate()
const u: Update<typeof value> = value |
@goodmind The expression Edit: It is theoretically possible as demonstrated in this code snippet, but given union/intersection types, you might prefer a dedicated operator anyways. type Xor<A if constraint A, B if constraint B> = [
case A extends true: B extends false,
default: B extends true,
];
type Disjoint<A, B> = [default U in: Xor<U extends A, U extends B>]; |
@isiahmeadows what is |
@goodmind I updated the proposal with some new types and cleaned it up quite a bit when I moved it to a gist. |
Thank you for the effort. Do you know what would be the next steps to get something like this implemented? |
@isiahmeadows it is sometimes cumbersome to read this gist (and |
@goodmind I'll update it with the constraint types explained better. I'll also introduce a high-level summary, to make it easier to understand. |
@goodmind Updated the gist. Also, the |
Gist updated with compile-time assertions |
Would this proposal allow someone to write an type/interface that represents just the readonly properties of a type? Like type WritableKeys<T> = // somehow?
type ReadonlyKeys<T> = // somehow?
type WritablePart<T> = { [K in WritableKeys<T>]: T[K] };
type ReadonlyPart<T> = { [K in ReadonlyKeys<T>]: T[K] };
type Identityish<T> = WritablePart<T> & Readonly<ReadonlyPart<T>>; so that you can write a safe version of function unsafeSet<T,K extends keyof T>(obj: T, prop: T, val: T[K]) {
obj[prop] = val; // what if T[K] is readonly?
} like function safeSet<T,K extends WritableKeys<T>>(obj: T, prop: T, val: T[K]) {
obj[prop] = val;
} Similarly for making an interface from a class, excluding any private members: type PublicKeys<T> = // somehow?
type ProtectedKeys<T> = // somehow?
type PrivateKeys<T> = // somehow?
type PublicPart<T> = { [K in PublicKeys<T>]: T[K] }; These are constraints on types but I'm not sure if there's any way to express them. If this is not the appropriate issue I'll find or create another one. Thanks! |
@jcalz No, because type-level property reflection is out of scope of this proposal. |
Thanks. EDIT: wait, couldn't you do something like type ReadonlyPart<T> = {
[K in keyof T]: [case ({K: T[K]} is {readonly K: T[K]}): T[K]];
} ? |
@jcalz That doesn't work (for readonly), for two reasons:
Assuming the latter and #15534 are both fixed, you would need to do this instead: type ReadonlyKeys<T> = keyof ReadonlyPart<T>;
type ReadonlyPart<T> = {
// Check if not assignable to read/write.
[K in keyof T]: [case !(T extends {[K]: T[K]}): T[K]];
}; For any other modifier (public, private, protected), it still remains impossible, because TypeScript does not expose those at all at the type level outside their scope. I thought there was likely a way with readonly properties, but couldn't come up with one initially. |
Are there any active plans to implement this proposal in TypeScript? |
@ajbouh Item of note: it's really only implementing part of it. Specifically, a variant of my proposal is being implemented right now:
The third point could be easily shimmed with some boilerplate in terms of the first, but the fourth currently cannot. As for a quick TL;DR, for those who aren't fully aware of what each one is, or how it's implemented:
* TS team: if there is an issue open for either of these two, please tell me so I can edit this in. |
I have been checking issues similar to this one lately. The Impossible type seems to be tracked in #23689 and I'm also waiting for support for upper-bound contraints ( |
Closing this issue as the TS team has basically implemented the majority of it through its conditional types, just with a different, more JS-like syntax. |
Edit: Now a gist, with all the previous edits erased.
See this gist for the current proposal. Better there than a wall of text here.
The text was updated successfully, but these errors were encountered: