-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
Constraint Types Proposal #13257
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: