Skip to content
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

How --strictAny will support any in a contravariant position? #24711

Closed
InExtremaRes opened this issue Jun 5, 2018 · 11 comments
Closed

How --strictAny will support any in a contravariant position? #24711

InExtremaRes opened this issue Jun 5, 2018 · 11 comments
Labels
Discussion Issues which may not have code impact

Comments

@InExtremaRes
Copy link

I'm very sorry if this is not the place for this, but since #24593 is closed I am opening a new, more specific issue.

This is all related to #24423 (--strictAny).

In #24593 @DanielRosenwasser said:

  • 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({ /*...*/ } as never)

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?)?

Thank you very much in advance.

@RyanCavanaugh RyanCavanaugh added the Discussion Issues which may not have code impact label Jun 5, 2018
@RyanCavanaugh
Copy link
Member

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.

@DanielRosenwasser
Copy link
Member

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.

@InExtremaRes
Copy link
Author

@RyanCavanaugh

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?

@InExtremaRes
Copy link
Author

@DanielRosenwasser

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?

@DanielRosenwasser
Copy link
Member

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

  1. General pain
  2. Widespread assumptions about bidirectional assignability for any (even in the compiler!)
  3. Reluctance to prescribe never.
  4. Incompatibility in existing declaration files.

@RyanCavanaugh
Copy link
Member

RyanCavanaugh commented 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 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.

@jack-williams
Copy link
Collaborator

@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.

@InExtremaRes
Copy link
Author

@jack-williams

[...] do mean generic in the sense that you can call it with any input and the output can be used in anyway?

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):

function setLogger(prinftLike: (m: string, ...args: any[]) => void) { /*...*/ }

Perhaps you have a curry function that currifies the first argument; without variadic types this can be:

function curry<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:

declare let f1: (arg: T1) => void;
declare let f2: (arg: T2) => void;
f1 = f2;

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:

declare function bar(a1: string, b2: number): void;
function foo1(fn: (...args: any[]) => void) { /*...*/ }
function foo2(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.

@jack-williams
Copy link
Collaborator

jack-williams commented Jun 6, 2018

@InExtremaRes

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:

declare function bar(a1: string, b2: number): void;
function foo1(fn: (...args: any[]) => void) { /*...*/ }
function foo2(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.

@weswigham
Copy link
Member

weswigham commented Jun 6, 2018

@DanielRosenwasser should we remove it from the 3.0 roadmap for now? Or are we still going to ship it there?

@DanielRosenwasser
Copy link
Member

@mhegazy removed it from the roadmap. I've elaborated on the decision here: #24737

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Discussion Issues which may not have code impact
Projects
None yet
Development

No branches or pull requests

5 participants