You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
How --strictAny will support any in a contravariant position? #24711
There are some issues with signatures like (x: any, y: any, ...zs: any[]) => any.
Now any is subject to contravariant checking.
Again, can tell users to use never instead of any.
For me this is a major concern since the pattern (...args: any[]) => any is now the best way to represent a generic function type.
I suppose this can be a generic type-safe function (in the type-safe sense that neither arguments nor return can be used freely without an assertion of some kind):
(...args: never[])=>unknown
Assuming an unknown type as described here #24439. Again as mentioned, never seems very counterintuitive in this scenarios, the same for
foo({/*...*/}asnever)
Semantically, that seems you telling "don't worry, type-checker, that value can never occur". I believe the weird part is the name never and not the semantics of never itself.
So I guess my questions are:
With the --strictAny flag enbled, which is the preferred idiom to any in a contravariant position? Will be never a more prominent type and suggested by the TypeScript team? Maybe there are plans to support a different pattern for this (should variadic types help here?)?
FWIW you can always comment in closed issues; we do read/reply to those, especially design meeting notes.
The PR notes mention special-casing ...args: any[], which I think is what we'd have to do.
Usually for never, I explain it as "the type of a value that is never observed", which is true of e.g. the return type of functions that always throw, but the explanation holds if you slightly reinterpret "observed" to mean "looked at" - you can have a value of type never so long as you never actually look directly at it.
For what it's worth, I don't think that --strictAny is a direction that we're going to be taking given the sorts of hoops it often forces users to jump through. Details to come.
FWIW you can always comment in closed issues; we do read/reply to those, especially design meeting notes.
Ok, thank you. I'll take note of that, I'm sorry 😊
The PR notes mention special-casing ...args: any[], which I think is what we'd have to do.
Yes, you're right. But since there was suggested the possibility to remove that exception and encourage to use never instead I asked about those.
Usually for never, I explain it as "the type of a value that is never observed", which is true of e.g. the return type of functions that always throw, but the explanation holds if you slightly reinterpret "observed" to mean "looked at" - you can have a value of type never so long as you never actually look directly at it.
I like that interpretation of never. I have to admit the code still looks a little strange, but I think it's just a thing of get used to it. Was never planned for this kind of uses?
For what it's worth, I don't think that --strictAny is a direction that we're going to be taking given the sorts of hoops it often forces users to jump through. Details to come.
Oh, well, I'll be expecting those details then.
Was the main issue the proliferation of any's in the libraries and type definitions out there?
In addition to the above, I think the results of turning on the flag inside the compiler were informative. Our general policy in our codebase is never to use any to "shut up" the type system, we generally write very explicit types, and we don't have any community-written .d.ts files mixing in "external" anys.
Turning the flag on found some assumptions in the tsconfig parser that no one has really noticed (i.e. it looks like you will crash the compiler if the top-level tsconfig content is the literal string "null"), and a bunch of what I would consider noise. There was one instance of new Array that we didn't realize was making an any[]. Every other strict flag we've turned on has found at least one real bug (or at least a "true unsoundness" where something only happened to work) with reasonably minimal noise.
I would honestly hate for this flag to be turned on automatically in a codebase I was maintaining - I just wouldn't expect it to yield any value; teams that are "careful" about not introducing anys tend to be very successful at having that not happen, and are only using any where they really don't get value from the type system.
Daniel Contreras (@InExtremaRes) Sorry for the silly question: when you say that (...args: any[]) => any is the best way to represent a generic function, do mean generic in the sense that you can call it with any input and the output can be used in anyway?
I know you define type-safe when you say generic type-safe, but I'm not sure what you mean by generic. I feel this is obvious to others, I'm just not really sure I understand all the issues with any in contravariant positions.
Perhaps you have a curry function that currifies the first argument; without variadic types this can be:
functioncurry<U,R>(fn: (a1: U, ...args: any[])=>R,a: U){return(...b: any[])=>fn(a, ...b);}
Or just any time you need a function of any kind, even if you are not going to call it yourself. I am not saying these are good patterns or that it is the best way to do it, just giving examples of what I mean.
I'm just not really sure I understand all the issues with any in contravariant positions.
As you probably known, with --strictFunctionTypes flag the arguments positions are checked contravariantly:
So for that last assignment to work T1 must be assignable to T2.
If any cannot be assigned to anything and without an exception as noted in the PR, then this would happen:
declarefunctionbar(a1: string,b2: number): void;functionfoo1(fn: (...args: any[])=>void){/*...*/}functionfoo2(fn: (...args: {}[])=>void){/*...*/}foo1(bar);// :)foo2(bar)// type '{}' not assignable to type 'string' :C
I am using the empty object type {} since is a type that cannot be assigned to anything but anything can be assigned to it, similar to how any could work in this situations with --strictAny enabled.
I am sorry if I'm over explaining here or I'm not answering your question at all.
I am sorry if I'm over explaining here or I'm not answering your question at all.
Everything is very clear, thank you! I appreciate the very detailed explanation. I now understand the problem. Where I was getting lost is that in your example:
declarefunctionbar(a1: string,b2: number): void;functionfoo1(fn: (...args: any[])=>void){/*...*/}functionfoo2(fn: (...args: {}[])=>void){/*...*/}foo1(bar);// :)foo2(bar)// type '{}' not assignable to type 'string' :C
I think of the any as being in a covariant position rather than contravariant; the any is contravariant in fn, which is itself contravariant in foo1.
Activity
RyanCavanaugh commentedon Jun 5, 2018
FWIW you can always comment in closed issues; we do read/reply to those, especially design meeting notes.
The PR notes mention special-casing
...args: any[], which I think is what we'd have to do.Usually for
never, I explain it as "the type of a value that is never observed", which is true of e.g. the return type of functions that always throw, but the explanation holds if you slightly reinterpret "observed" to mean "looked at" - you can have a value of typeneverso long as you never actually look directly at it.DanielRosenwasser commentedon Jun 6, 2018
For what it's worth, I don't think that
--strictAnyis a direction that we're going to be taking given the sorts of hoops it often forces users to jump through. Details to come.InExtremaRes commentedon Jun 6, 2018
Ryan Cavanaugh (@RyanCavanaugh)
Ok, thank you. I'll take note of that, I'm sorry 😊
Yes, you're right. But since there was suggested the possibility to remove that exception and encourage to use
neverinstead I asked about those.I like that interpretation of
never. I have to admit the code still looks a little strange, but I think it's just a thing of get used to it. Wasneverplanned for this kind of uses?InExtremaRes commentedon Jun 6, 2018
Daniel Rosenwasser (@DanielRosenwasser)
Oh, well, I'll be expecting those details then.
Was the main issue the proliferation of
any's in the libraries and type definitions out there?DanielRosenwasser commentedon Jun 6, 2018
I think a lot of the conversation in the design meeting notes actually elaborates on it, but very offhandedly, I'd sum it up as
any(even in the compiler!)never.RyanCavanaugh commentedon Jun 6, 2018
In addition to the above, I think the results of turning on the flag inside the compiler were informative. Our general policy in our codebase is never to use
anyto "shut up" the type system, we generally write very explicit types, and we don't have any community-written .d.ts files mixing in "external"anys.Turning the flag on found some assumptions in the tsconfig parser that no one has really noticed (i.e. it looks like you will crash the compiler if the top-level tsconfig content is the literal string
"null"), and a bunch of what I would consider noise. There was one instance ofnew Arraythat we didn't realize was making anany[]. Every other strict flag we've turned on has found at least one real bug (or at least a "true unsoundness" where something only happened to work) with reasonably minimal noise.I would honestly hate for this flag to be turned on automatically in a codebase I was maintaining - I just wouldn't expect it to yield any value; teams that are "careful" about not introducing
anys tend to be very successful at having that not happen, and are only usinganywhere they really don't get value from the type system.jack-williams commentedon Jun 6, 2018
Daniel Contreras (@InExtremaRes) Sorry for the silly question: when you say that
(...args: any[]) => anyis the best way to represent a generic function, do mean generic in the sense that you can call it with any input and the output can be used in anyway?I know you define type-safe when you say generic type-safe, but I'm not sure what you mean by generic. I feel this is obvious to others, I'm just not really sure I understand all the issues with
anyin contravariant positions.InExtremaRes commentedon Jun 6, 2018
Jack Williams (@jack-williams)
Yes, basically. Maybe you are trying to constraint a generic to be an any function type, just like the
ReturnType<T>type.Maybe you need to pass a printf-like function (don't know why):
Perhaps you have a curry function that currifies the first argument; without variadic types this can be:
Or just any time you need a function of any kind, even if you are not going to call it yourself. I am not saying these are good patterns or that it is the best way to do it, just giving examples of what I mean.
As you probably known, with
--strictFunctionTypesflag the arguments positions are checked contravariantly:So for that last assignment to work
T1must be assignable toT2.If
anycannot be assigned to anything and without an exception as noted in the PR, then this would happen:I am using the empty object type
{}since is a type that cannot be assigned to anything but anything can be assigned to it, similar to howanycould work in this situations with--strictAnyenabled.I am sorry if I'm over explaining here or I'm not answering your question at all.
jack-williams commentedon Jun 6, 2018
Daniel Contreras (@InExtremaRes)
Everything is very clear, thank you! I appreciate the very detailed explanation. I now understand the problem. Where I was getting lost is that in your example:
I think of the
anyas being in a covariant position rather than contravariant; theanyis contravariant infn, which is itself contravariant infoo1.weswigham commentedon Jun 6, 2018
Daniel Rosenwasser (@DanielRosenwasser) should we remove it from the 3.0 roadmap for now? Or are we still going to ship it there?
DanielRosenwasser commentedon Jun 7, 2018
Mohamed Hegazy (@mhegazy) removed it from the roadmap. I've elaborated on the decision here: #24737