GeezerVison is showing github.com — Predefined conditional types by ahejlsberg · Pull Request #21847 · microsoft/TypeScript
Conversation
This PR adds several predefined conditional types to lib.d.ts:
Exclude<T, U> -- Exclude from T those types that are assignable to U.
Extract<T, U> -- Extract from T those types that are assignable to U.
NonNullable<T> -- Exclude null and undefined from T.
ReturnType<T> -- Obtain the return type of a function type.
InstanceType<T> -- Obtain the instance type of a constructor function type.
Some examples:
type T00 = Exclude<"a" | "b" | "c" | "d", "a" | "c" | "f">; // "b" | "d"
type T01 = Extract<"a" | "b" | "c" | "d", "a" | "c" | "f">; // "a" | "c"
type T02 = Exclude<string | number | (() => void), Function>; // string | number
type T03 = Extract<string | number | (() => void), Function>; // () => void
type T04 = NonNullable<string | number | undefined>; // string | number
type T05 = NonNullable<(() => string) | string[] | null | undefined>; // (() => string) | string[]
function f1(s: string) {
return { a: 1, b: s };
}
class C {
x = 0;
y = 0;
}
type T10 = ReturnType<() => string>; // string
type T11 = ReturnType<(s: string) => void>; // void
type T12 = ReturnType<(<T>() => T)>; // {}
type T13 = ReturnType<(<T extends U, U extends number[]>() => T)>; // number[]
type T14 = ReturnType<typeof f1>; // { a: number, b: string }
type T15 = ReturnType<any>; // any
type T16 = ReturnType<never>; // any
type T17 = ReturnType<string>; // Error
type T18 = ReturnType<Function>; // Error
type T20 = InstanceType<typeof C>; // C
type T21 = InstanceType<any>; // any
type T22 = InstanceType<never>; // any
type T23 = InstanceType<string>; // Error
type T24 = InstanceType<Function>; // Error
The Exclude type is a proper implementation of the Diff type suggested here #12215 (comment). We've used the name Exclude to avoid breaking existing code that defines a Diff, plus we feel that name better conveys the semantics of the type. We did not include the Omit<T, K> type because it is trivially written as Pick<T, Exclude<keyof T, K>>.
When applying ReturnType<T> and InstanceType<T> to types with multiple call or construct signatures (such as the type of an overloaded function), inferences are made from the last signature (which, presumably, is the most permissive catch-all case). It is not possible to perform overload resolution based on a list of argument types (this would require us to support typeof for arbitrary expressions, as suggested in #6606, or something similar).
Fixes #19569.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
vincentbel, vhfmag, KiaraGrouwstra, bogomya, ibezkrovnyi, robotlolita, andrekosak, wanganjun, kastermester, no23reason, and 68 more reacted with thumbs up emoji
marcind, johnfn, pelotom, chrismcleod, wand3r, Pajn, dfilatov, mrmlnc, gallayl, olaf89, and 61 more reacted with hooray emoji
karol-majewski, piotrwitek, listepo-alterpost, louy, davidkpiano, andrewbranch, mightyiam, aikoven, vhfmag, KiaraGrouwstra, and 36 more reacted with heart emoji
All reactions
-
👍
78 reactions
-
🎉
71 reactions
-
❤️
46 reactions
We did not include the Omit<T, K> type because it is trivially written as Record<T, Exclude<keyof T, K>>.
I think that should have been Pick rather than Record. :)
Moreover, thanks for all the new features!
|
sebald reacted with thumbs up emoji
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
@tycho01 Right you are! Fixed.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
I wonder if it is possible to detect plain objects using the Exclude type? Something like:
const plainObj = {foo: 'bar'}
const nonPlainObj = new Error()
IsPlainObject<typeof plainObj> // true
IsPlainObject<typeof nonPlainObj> // false
IsPlainObject<string> // false
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
Aria (@AriaMinaei): for what it's worth, I'm not aware of any way we can do that so far. I think it comes down to getting a prototype, but as far as I know the type-level operators we have don't do much with JS prototypes.
Out of curiosity, what's your use-case?
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
@tycho01 Thanks, now I know I shouldn't try more :)
Here is my use-case though:
export type Atomify<V> = {
'1':
V extends Array<infer T> ? ArrayAtom<Atomify<T>> :
V extends AbstractAtom<$IntentionalAny> ? V :
// following is commented out as I don't know how to detect plain objects in TS
// V extends {constructor: Function} ? BoxAtom<V> :
V extends object ? DictAtom<{[K in keyof V]: Atomify<V[K]>}> :
BoxAtom<V>
}[V extends number ? '1' : '1']
const atomifyDeep = <V extends {}>(jsValue: V): Atomify<V> => {
if (Array.isArray(jsValue)) {
return fromJSArray(jsValue)
} else if (isPlainObject(jsValue)) {
return fromJSObject(jsValue as $IntentionalAny)
} else if (jsValue instanceof AbstractAtom) {
return jsValue as $IntentionalAny
} else {
return fromJSPrimitive(jsValue)
}
}
const fromJSArray = (jsArray: $IntentionalAny): $IntentionalAny => {
return new ArrayAtom(jsArray.map(atomifyDeep))
}
const fromJSObject = (jsObject: {[key: string]: mixed}): $IntentionalAny => {
return new DictAtom(mapValues(jsObject, atomifyDeep))
}
const fromJSPrimitive = (jsPrimitive: mixed): $IntentionalAny => {
return new BoxAtom(jsPrimitive)
}
export default atomifyDeep
This atomifyDeep() function takes any js value as input, and returns a "reactive" data structure. Arrays and plain objects are deeply wrapped into these structures which tracks their changes deeply, while JS primitives, functions, and instances of any class are wrapped in a BoxAtom which tracks their changes via reference.
As you see, I used your [V extends number ? '1' : '1'] trick to recursively call AtomifyDeep, and it passes all of my type test cases, except those for non-plain JS objects. It detects them as plain JS objects and wraps them in a DictAtom.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
Why are the "other" cases for ReturnType and InstanceType any and not never?
When would you want ReturnType<"notafunction"> to be any? This seems really bad since then you would just propagate any everywhere if you make a mistake while using ReturnType somwhere.
|
KiaraGrouwstra, paldepind, tommytroylin, leonadler, PgBiel, and eugirdor reacted with thumbs up emoji
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Mar 2, 2018
JasonKleban
commented
Mar 6, 2018
•
edited
Loading
Uh oh!
There was an error while loading. Please reload this page.
|
Awesome!!
I get a few errors attempting to combine the use of type mapping with ReturnType. Is there a way to assert that T[M] will be compatible as the generic argument to ReturnType?
interface X {
a : (,) => Promise<string>
b : (,,,) => Promise<number>
c : () => Promise<UserData>
}
type Cached<T> = { [M in keyof T] : ReturnType<T[M]> } // ❌ Type 'T[M]' does not satisfy the constraint '(...args: any[]) => any'.
const ex : Cached<X> = <any>null;
type Cached2<T extends { [m : string] : (...args: any[]) => any }> = { [M in keyof T] : ReturnType<T[M]> }
const ex2 : Cached2<X> = <any>null; // ❌ Index signature is missing in type 'X'.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
Nevermind. WOW!! This works:
type Cached<T> = { [M in keyof T] : ReturnType<T[M] extends (...args: any[]) => any ? T[M] : any> }
const ex : Cached<X> = <any>null;
And so does this, to extract the return type out of the promise!! Amazing
type Cached2<T> = { [M in keyof T] : ReturnType<T[M] extends (...args: any[]) => any ? T[M] : any> extends Promise<infer Y> ? Y : ReturnType<T[M] extends (...args: any[]) => any ? T[M] : any> }
const ex2 : Cached2<X> = <any>null;
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
I am playing around with this, and I would like to know a type mapper that resembles Flow $Diff, meaning, one that returns properties that are in both, T and U, but, without expressing T nor U as union types, but intersecting their own properties.
Edit: I mean to say, that $Diff returns a property exclusion between the properties of A, and the properties of B. As in the example below, where I also use the work 'intersection' although is quite the opposite as the real thing I wanted to say. Sorry.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
@tycho01 Almost! Thanks you so much! I test it, and is just
class A {
a: string;
b: string;
}
class B {
a: string;
c: string;
}
type Diff<T, U> = Pick<T, Exclude<keyof T, keyof U>>;
type C = Diff<A, B>;
// C = { b: string; }
Maybe I did not explain myself, but $Diff in Flow is a intersection exclusion of A and B properties.
|
KiaraGrouwstra and jedrzejginter reacted with thumbs up emoji
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
Any chance for Typescript to have a type mapper with this? Although the naming will be harder than usual.
|
KiaraGrouwstra reacted with thumbs up emoji
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
ReturnType design doesn't support generic functions, right?
Is there a syntax that would work for this:
// Doesn't work in 2.8
function f<T>(x: T): T { return x }
type R = ReturnType<typeof f<string>>; // = string
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
ReturnType design doesn't support generic functions, right?
it is not ReturnType, values can not have generic type arguments. type arguments are only available in certain syntactic locations, like call/new expressions, extends clauses and type references.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
Mohamed Hegazy (@mhegazy) That's what I figured, thanks.
Should I open a suggestion for allowing a type argument when a generic function is used in typeof?
ReturnType is a good use case for that.
I know there is an open suggestion for allowing arbitrary expression in typeof anyway, would it be enough?
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
typeof is not the issue either. typeof allows a limited subset of expression syntax. I think #15877 is what you are looking for.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
Mohamed Hegazy (@mhegazy) about the PR, do you plan to include Omit type helper. I know how to implemented, but still I think Omit is commonly used and will be worth to include it.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
do you plan to include Omit type helper.
No. we have avoided to use the names Omit and Diff to avoid breaking users who already defined these types. Omit is trivially implemented as Pick<T, Exclude<keyof T, K>>.
|
SlurpTheo reacted with thumbs up emoji
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Reviewers
1 more reviewer
Reviewers whose approvals may not affect merge requirements
Uh oh!
There was an error while loading. Please reload this page.
Assignees
No one assigned
Milestone
No milestone
Development
Successfully merging this pull request may close these issues.
Uh oh!
There was an error while loading. Please reload this page.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
|