-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Intersection types and string literals #9410
Comments
There are two things that you might consider issues. The first is that Another is that the way that we check the contextual type for the strings In this case, you don't have a union with string literal types as constituents. You have an intersection type (with a constituent that is a union which has a string literal types as constituents). You could make the argument that we should be digging deeper into the contextual type, but I don't really think it makes sense. You can never satisfy a string literal type that is intersected with anything else, so really I consider subtype reduction to be the source of the issue. |
This definitely makes sense for intersecting it with anything other than other string literals,but what about this case? var myVal: ("A" | "B") & ("A" | "C")
myVal = "A" This also currently fails typechecking. (Also, I should make a note that I fully acknowledge that these are rather niche corner cases of the type system :) the first one I pointed out does actually come up in a real world use case though.) |
I'm not quite sure what the intent of You could argue that we should perform supertype reduction for intersection types just like we perform subtype reduction for union types. Specifically, that we should reduce |
I'll elaborate a bit on our use case (which is, admittedly, a bit of type system abuse...) For many of our types, they come in two varieties: design time view and runtime view. Thus, we might have the following two types: class DesignTimeTuple<T> {
_value: T
_previousValue: T
_isChanged: boolean
}
class Person {
name: string
salary: number
}
class DesignTimePerson {
name: DesignTimeTuple<string>
salary: DesignTimeTuple<number>
} This pattern happens over and over, and in every case the properties on the type, and the underlying type that they take are identical, meaning we have two places to maintain the signature of this type. One way to encode this to unify the two types would be: class Person {
name: string | DesignTimeTuple<string>
salary: number | DesignTimeTuple<number>
} However, this doesn't enforce that all properties are either runtime values or design time values (i.e. you can mix and match). Similarly, a function can't indicate that it plans to return the design time view. The trick we came up with is to have something like the following: /* Runtime needs to be restricted to a set of primitive types we support so that DesignTimeTuple below isn't considered a valid runtime type */
type Runtime = string | number | boolean
class DesignTime {
private token: "token" // To make DesignTime only apply to things that extend DesignTime
}
class DesignTimeTuple<T extends Runtime> extends DesignTime {
_value: T
_previousValue: T
_isChanged: boolean
}
type Value<T,U> = (T | DesignTimeTuple<T>) & U // <--
class Person<U extends Runtime | DesignTime> {
name: Value<string, U>
salary: Value<number, U>
} This means that U effectively restricts the type This works great, up until we have something like We're still debating, as a team, whether this abuse of the type system is worth the safety it provides, or whether it just overcomplicates things; however, it was something that surprised us, so we decided to raise it here in case it was actually a case that had been overlooked. |
The particular case in the OP seems to have been addressed a while ago. Now: declare var myInstance: MyClass<"A" | "B">;
myInstance.value = "A"; // okay as desired
myInstance.value = "B"; // okay as desired
myInstance.value = "other value"; // error as desired |
Though it has no effect on the scenario in the OP, the type of |
I agree. I have asked for such things before, and the reply was something like it's not a big deal, as long as concrete values behave properly. And I see the point of that. If |
We have come around on this issue a bit since you first filed it :D |
Should be fixed by #23751 |
TypeScript Version: 1.8.0
Code
Expected behavior:
(prefix: I'm not sure if this was intentional or not, or whether it's a known issue or not, and I looked through other issues looking for mention of it and didn't see anything)
Case 1 and 2 should be valid, while case 3 should be an error.
Actual behavior:
All three cases are errors, as the intersection results in an empty type, rather than the type "A" | "B"
From one perspective, the type "string" is a super set of type "A" | "B".
This might also impact the recent work done in #9407
If this was intentional behavior, i'll edit this issue into a question as to why that decision was made, so it can serve as future documentation for anyone else searching for this issue.
The text was updated successfully, but these errors were encountered: