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

Partial<any> should be treated as any #20247

Closed
unional opened this issue Nov 24, 2017 · 21 comments
Closed

Partial<any> should be treated as any #20247

unional opened this issue Nov 24, 2017 · 21 comments
Labels
Duplicate An existing issue was already created

Comments

@unional
Copy link
Contributor

unional commented Nov 24, 2017

[email protected]

let x: Partial<any>
let y: number = x // Type 'Partial<any>' is not assignable to type 'number'.
let z: string = x // Type 'Partial<any>' is not assignable to type 'string'.

Partial<any> should be considered as any and can be assigned to other types.

Playground

@kitsonk
Copy link
Contributor

kitsonk commented Nov 24, 2017

Do you have a real world use case for this?

It appears to be intentional because Partial<any> is the only one that actually generates a general indexer:

type PartialString = Partial<string>; // string
type PartialNumber = Partial<number>; // number
type PartialObject = Partial<object>; // object
type PartialWeakObject = Partial<{}>; // {}
type PartialAny = Partial<any>; // { [x: string]: any; }

@unional
Copy link
Contributor Author

unional commented Nov 24, 2017

@Jack-Works
Copy link
Contributor

By the way, type number & 123 should be treated as 123, string & 'abc' should be 'abc'

@mhegazy
Copy link
Contributor

mhegazy commented Nov 27, 2017

Partial and Record are mapped types, and these assume a transformation on the members of an object type.. there is a special treatment for primitives as noted by @kitsonk , since mapping primitive properties is rather meaningless.

Now, any can be thought of as an infinite union of all possible types, that include both primitive and object types.. a mapping operation on that infinite union would ignore primitives (number, string, etc..) and would only operate on the object types. the result of that transformation is guaranteed to always be an object type; the names of the properties is the infinite set of all possible property names (string) and their types are all possible types (any) hence the result is `{ [x: string]: any }.

I think there is something to be said on the use of anyas an argument to Partial.. either you need a generic type parameter threaded through to be able to do the transformation correctly, or it should be explicitly specified as any and avoid the confusion.

Please see #19185 for more details about the change.

Duplicate of #19624

@mhegazy mhegazy added the Duplicate An existing issue was already created label Nov 27, 2017
@unional
Copy link
Contributor Author

unional commented Nov 27, 2017

I think #19185 may be over-solving the issue in #19152.

I can understand that { [P in any]: T } and { [P in keyof any]: T } should yield { [x: string]: T}

But that doesn't mean Partial<any> or ReadOnly<any> should resolve to { [x: string]: any } and { readonly [x: string]: any }

In #19152, the problem is the indexer, not the property type, that have the problem.

Also, Partial<any> and ReadyOnly<any> has a semantic meaning. In these circumstances, I think your statement "any can be thought of as an infinite union of all possible types, that include both primitive and object types" would be a more perceivable take.

I understand that Partial and Recordare mapped types, but as they need special treatment for primitives, I think they also need special treatment for any. 🌷

@amir-arad
Copy link

amir-arad commented Nov 28, 2017

a use-case equivalent to this:

function consumer(p:{foo:string}){}
const obj :Partial<any> = {foo:'bar'};
consumer(obj);  // TS2345:Argument of type 'Partial<any>' is not assignable to parameter of type '{ foo: string; }'. Property 'foo' is missing in type 'Partial<any>'.

currently blocks wix-incubator/wix-react-tools#188

@kitsonk
Copy link
Contributor

kitsonk commented Nov 28, 2017

@amir-arad any change here wouldn't solve the issue with your code. It is unrelated. For example the following is an error:

function consumer(p:{foo:string}){}
const obj: Partial<{ foo: string }> = {foo:'bar'};
consumer(obj);

You have a situation where the key you require is optional on the source type. That won't ever work.

@amir-arad
Copy link

amir-arad commented Nov 28, 2017

from the Documentation site :

The any type is a powerful way to work with existing JavaScript, allowing you to gradually opt-in and opt-out of type-checking during compilation.

I expect Partial to be consistent in that matter with other type expressions, such as unions and intersections, where any plays that special role:

function consumer(p:{foo:string}){}
const obj: (any & {foo:null}) | {foo:number} = {foo:'bar'};
consumer(obj); // this works fine

@unional
Copy link
Contributor Author

unional commented Nov 28, 2017

I can understand that { [P in any]: T } and { [P in keyof any]: T } should yield { [x: string]: T}

To nail this a bit more, I can see the rationale for #19185

type X<T, R> = {
    [P in keyof T]?: R;
};

let x: X<{ a: number, b: string }, { foo: number }> = { a: { foo: 1 }, b: { foo: 2 } }

// y: { [P: string]: { foo: number } }
let y: X<any, { foo: number }> = { c: { foo: number } }

// z: { a: any, b: any }
let z: X<{ a: number, b: string }, any> = { a: 'a', b: 1 }

But Partial<T>, ReadOnly<T>, or some others should behave more than mapped type, as they have special cases for primitives.

@mhegazy
Copy link
Contributor

mhegazy commented Nov 28, 2017

But Partial, ReadOnly, or some others should behave more than mapped type, as they have special cases for primitives.

all mapped types have the same behavior for primitives..

@kitsonk
Copy link
Contributor

kitsonk commented Nov 28, 2017

I expect Partial to be consistent in that matter with other type expressions, such as unions and intersections, where any plays that special role

Then why not use any instead of Partial<any>?

@unional
Copy link
Contributor Author

unional commented Nov 28, 2017

all mapped types have the same behavior for primitives..

fair enough. As I said, I agree that the current behavior for mapped type is "likely" to be fine.

type X<T> = {
  [P in keyof T]: T[P]
}

X<any> => { [P in keyof any]: any[P] } => { [P: string]: any }

My question is: should Partial and Readonly be just mapped type? Or should they be something more than a mapped type?

"a partial set of an infinite set is an infinite set"
"ready only of an infinite set is read only of an infinite set"

@mhegazy
Copy link
Contributor

mhegazy commented Nov 28, 2017

Mapped types in general are defined in terms of object types. in that context Readonly is an approximation of what a Readonly type operator would ideally mean. for instance Readonly<Array<T>> !== ReadonlyArray<T>, and so is Map and Set. so in that sense, i think Readonly<any> behaving as any in TS 2.5 was a bug, an unintentional behavior.

@unional
Copy link
Contributor Author

unional commented Nov 29, 2017

Yes, on that argument it does make sense for going against Readonly<any> => any

I'm almost convinced to drop Readonly<> from this discussion.

But on the other hand 😛 :

const x: Readonly<number> = 1
const y: Readonly<number & any> = 1 // error

any should be an inclusive of all types.

Maybe the special treatment needed for primitives is the root cause of this confusing behavior.

Maybe type Readonly<T extends object> = { ... } would avoid this problem altogether.

Need more time to think about Partial<any>

EDIT: while Readonly<> feels like a type operator, it is currently worked as mapped type, so:

let x: Readonly<{ foo: number }> => x: { readonly foo: number }
// !=
x: readonly { foo: number } 
// or
x: readonly { readonly foo: number }

But

let x: Readonly<number> => x: number?
// or even
x: readonly number

that special treatment for primitive does create some problems. It feels like an escape clause then some actually desirable behaviors.

@mhegazy
Copy link
Contributor

mhegazy commented Nov 29, 2017

Maybe type Readonly<T extends object> = { ... } would avoid this problem altogether.

we wanted to do that originally. but then 1. object was not in back then, and 2. felt annoying that every time you would write Partial<T> you have to make sure T extends object. So instead we have added the special handling of primitives. back then we did not really think of any deeply, and the behavior was rather unspecified. last release we fixed that, but really it should have been like so all along.

@amir-arad
Copy link

@kitsonk It's not always possible or convenient to define the type directly. sometimes you need to work with a third party tool, that returns Partial<T>. using any as the value of <T> should opt-out of type-checking during compilation.

@kitsonk
Copy link
Contributor

kitsonk commented Nov 29, 2017

@amir-arad

declare function foo<T>(bar: T): Partial<T>;
const baz: any = { qat: 1 };
const qat = foo(baz) as any;
// or
const qux: any = foo(baz);

It just requires you to be explicit about your any usage, which you should anyways.

@amir-arad
Copy link

amir-arad commented Nov 30, 2017

@kitsonk as any has a tendency to stick around long after it's usefulness.
I don't think It's a good solution, explicitly opting out of type checks for the same piece of data, again and again in the same code block.

const baz: any = { qat: 1 };
const qat = func1(func2(foo(baz) as any) as any) as any;  // this is not good code

@typescript-bot
Copy link
Collaborator

Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed.

@unional
Copy link
Contributor Author

unional commented Jan 2, 2018

anti-bot: this should be re-open for evaluation. 😛
It would be a breaking change but may worth the cost.
And ReadOnly and Partial may be treated differently, thus not a duplication.

@KiaraGrouwstra
Copy link
Contributor

gcanti/typelevel-ts#19 suggested using conditional types to accomplish the desired behavior.

@microsoft microsoft locked and limited conversation to collaborators Jul 3, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests

7 participants