5

[EDIT] This question is indeed duplicate; see @jcalz's answer on the linked post :)

Related discussion on TS github: Allowing narrow from any


playground

When I do { a: any } & { a: number }, TypeScript resolves it to { a: any }. However, considering the meaning of intersection type, I think it should be { a: number }, as only values in number type are actually in the intersection of those two types. So I'm not sure about the rationale behind this behavior - maybe it's because any is treated specially?

  • @jcalz you're right; typescript calls it so. Logically (set-theory), it just seems the opposite. – Meirion Hughes Oct 25 '17 at 13:52
  • @MeirionHughes since you deleted your previous comments, I deleted mine as well so it didn't look like I was a crazy person who talks to himself (which I am, but I don't like it to look that way). In any case the creators of TypeScript are well-versed in set theory/type theory/logic so I think you are confused about what makes a union and intersection. Quite possibly you're thinking of property keys, which are [contravariant with their types](https://github.com/Microsoft/TypeScript/issues/12625#issuecomment-264642613). – jcalz Oct 25 '17 at 14:35
  • yeah sorry, don't want to swamp a question with unrelated comments, so I generally end up deleting them after a while. I think this 'confusion' merits a question. I'll cc you so you can answer if you want. – Meirion Hughes Oct 25 '17 at 14:46
  • @jcalz Yeah it seems like your answer is what I was looking for. I searched for quite a time, assuming someone has already asked the question, but somehow couldn't find the it. Thanks for the answer ;) – Ahn Heejong Oct 26 '17 at 01:30

2 Answers2

1

Type intersection

When you intersect two types using T1 & T2, the resulting type has of all the properties in T1, as well as all of those in T2.

If you change your example slightly, to use string instead of any:

type AType = { a: string } & { a: number }

declare const a: AType
a.a // type: string & number

we see that a.a has the combination of the properties of string and number.

In your example, you might expect it to say that a.a was of type any & number, but that's just the same as any.

Type intersection with any

When a type (e.g. number) is intersected with any, any behaves as a subtype (a narrower type, with all the properties of number plus some more). You could say that any extends number. This leads us to the same behaviour as we see below:

type T1 = { a: number, b: number }
type T2 = { a: number }

type Intersection = T1 & T2 // T1 is a subtype of T2, so we get T1

Admittedly, this is unintuitive from the perspective that we expect any to be a wider (more permissive) type, rather than a narrower one. It is a special case.

You would see your expected behaviour if you used {} (the most permissive type) rather than any, because {} adds no properties to number.

Community
  • 1
  • 1
Tom Fenech
  • 72,334
  • 12
  • 107
  • 141
  • It doesn't look like this answers the question as asked, which is something like "Why is `any & number` treated as `any` instead of `number`?" You've just asserted that it is, but said nothing about why. – jcalz Oct 25 '17 at 13:23
  • @jcalz I see what you mean! What do you think now? – Tom Fenech Oct 25 '17 at 14:05
  • This is better, although a subtype with more properties is considered *narrower*, not *wider*. That is, every object of type `{foo: string, bar: number}` is a valid object of type `{foo: string}`, but not vice-versa. So the type with more properties is more restrictive... narrower. And `any` is intentionally unsound... it behaves as both a subtype and supertype of everything else, so `number | any` is `number & any` is `any`... no other type works that way. – jcalz Oct 25 '17 at 14:11
  • 1
    Yeah, I've corrected to "narrower" but I think that the rest is valid. I guess really, the answer boils down to "because that's how the language was designed". – Tom Fenech Oct 25 '17 at 14:32
  • Thanks for the answer. As you said, "because that's how the language was designed", that is. Too bad in this case I'm trying to override a library author's `any` so I guess there aren't much I can do about it haha – Ahn Heejong Oct 26 '17 at 01:32
1

It's because you are using any at first place which is supertype of number, what you using is type checking of any and number, so means it can be anything, which means it's any!

So any override the number type as it's supertype of number...

In typescript, always use specific type, if not sure about the type, use any...

So putting check for any is exactly like not putting any type check at all...

This is exact words from typescript team for any...

Any

We may need to describe the type of variables that we do not know when we are writing an application. These values may come from dynamic content, e.g. from the user or a 3rd party library. In these cases, we want to opt-out of type-checking and let the values pass through compile-time checks. To do so, we label these with the any type:
let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false; // okay, definitely a boolean

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. You might expect Object to play a similar role, as it does in other languages. But variables of type Object only allow you to assign any value to them - you can’t call arbitrary methods on them, even ones that actually exist:

let notSure: any = 4;
notSure.ifItExists(); // okay, ifItExists might exist at runtime
notSure.toFixed(); // okay, toFixed exists (but the compiler doesn't check)

let prettySure: Object = 4;
prettySure.toFixed(); // Error: Property 'toFixed' doesn't exist on type 'Object'

The any type is also handy if you know some part of the type, but perhaps not all of it. For example, you may have an array but the array has a mix of different types:

let list: any[] = [1, true, "free"];

list[1] = 100;
Alireza
  • 100,211
  • 27
  • 269
  • 172
  • This answer isn't exactly right. You say that `any` is broader than `number`, so `any & number` is the same as `any`. But `number | string` is broader than number, while `(number | string) & number` is just `number`. In fact, the issue is that `any` is intentionally unsound and treated as both a supertype *and* a subtype of every other type. For more info see [my answer](https://stackoverflow.com/a/46673732/2887218) to a previous question on this topic. – jcalz Oct 25 '17 at 13:40
  • That's exactly what I mean in simple words, but may not convey it exactly, I'll reword it, tnx @jcalz – Alireza Oct 26 '17 at 05:29