-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Type relationships for intersections with union constraints #23672
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
Changes from all commits
ef8d509
d90d6b9
e091e35
50c7ff7
b78054d
3707f7d
8a7c203
8442a45
bbcb1bb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3613,6 +3613,9 @@ namespace ts { | |
BooleanLike = Boolean | BooleanLiteral, | ||
EnumLike = Enum | EnumLiteral, | ||
ESSymbolLike = ESSymbol | UniqueESSymbol, | ||
VoidLike = Void | Undefined, | ||
/* @internal */ | ||
DisjointDomains = NonPrimitive | StringLike | NumberLike | BooleanLike | ESSymbolLike | VoidLike | Null, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Object types can also have disjoint domains, eg type A = { x: string };
type B = { x: number };
type C = { x: boolean };
function f5<T extends A | B>(x: T & (B | C)) {
const y: B = x; // expected to work, currently does not, doesn't work with this PR
} (important if you just consider each of those types of What we have here is certainly better, but definitely still not quite complete. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, the intent here is simply to do better for the primitive types. I don't think it is feasible to reason about disjoint domains for arbitrary object types since they can be recursive or infinite. Also, we still allow stuff like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I feel like you can justify that by claiming that Also, it's probably feasible for object types - we do compare them based on that structure, after all, and we're even within that comparison when this simplifier/inliner gets called. And it's not like I don't suppose we need to fix it as part of this (after all, it's already better than |
||
UnionOrIntersection = Union | Intersection, | ||
StructuredType = Object | Union | Intersection, | ||
TypeVariable = TypeParameter | IndexedAccess, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
tests/cases/conformance/types/intersection/intersectionWithUnionConstraint.ts(7,9): error TS2322: Type 'T & U' is not assignable to type 'string | number'. | ||
Type 'string | undefined' is not assignable to type 'string | number'. | ||
Type 'undefined' is not assignable to type 'string | number'. | ||
Type 'T & U' is not assignable to type 'number'. | ||
tests/cases/conformance/types/intersection/intersectionWithUnionConstraint.ts(8,9): error TS2322: Type 'T & U' is not assignable to type 'string | null'. | ||
Type 'string | undefined' is not assignable to type 'string | null'. | ||
Type 'undefined' is not assignable to type 'string | null'. | ||
Type 'T & U' is not assignable to type 'string'. | ||
tests/cases/conformance/types/intersection/intersectionWithUnionConstraint.ts(10,9): error TS2322: Type 'T & U' is not assignable to type 'number | null'. | ||
Type 'string | undefined' is not assignable to type 'number | null'. | ||
Type 'undefined' is not assignable to type 'number | null'. | ||
Type 'T & U' is not assignable to type 'number'. | ||
tests/cases/conformance/types/intersection/intersectionWithUnionConstraint.ts(11,9): error TS2322: Type 'T & U' is not assignable to type 'number | undefined'. | ||
Type 'string | undefined' is not assignable to type 'number | undefined'. | ||
Type 'string' is not assignable to type 'number | undefined'. | ||
Type 'T & U' is not assignable to type 'number'. | ||
tests/cases/conformance/types/intersection/intersectionWithUnionConstraint.ts(12,9): error TS2322: Type 'T & U' is not assignable to type 'null | undefined'. | ||
Type 'string | undefined' is not assignable to type 'null | undefined'. | ||
Type 'string' is not assignable to type 'null | undefined'. | ||
Type 'T & U' is not assignable to type 'null'. | ||
|
||
|
||
==== tests/cases/conformance/types/intersection/intersectionWithUnionConstraint.ts (5 errors) ==== | ||
function f1<T extends string | number, U extends string | number>(x: T & U) { | ||
// Combined constraint of 'T & U' is 'string | number' | ||
let y: string | number = x; | ||
} | ||
|
||
function f2<T extends string | number | undefined, U extends string | null | undefined>(x: T & U) { | ||
let y1: string | number = x; // Error | ||
~~ | ||
!!! error TS2322: Type 'T & U' is not assignable to type 'string | number'. | ||
!!! error TS2322: Type 'string | undefined' is not assignable to type 'string | number'. | ||
!!! error TS2322: Type 'undefined' is not assignable to type 'string | number'. | ||
!!! error TS2322: Type 'T & U' is not assignable to type 'number'. | ||
let y2: string | null = x; // Error | ||
~~ | ||
!!! error TS2322: Type 'T & U' is not assignable to type 'string | null'. | ||
!!! error TS2322: Type 'string | undefined' is not assignable to type 'string | null'. | ||
!!! error TS2322: Type 'undefined' is not assignable to type 'string | null'. | ||
!!! error TS2322: Type 'T & U' is not assignable to type 'string'. | ||
let y3: string | undefined = x; | ||
let y4: number | null = x; // Error | ||
~~ | ||
!!! error TS2322: Type 'T & U' is not assignable to type 'number | null'. | ||
!!! error TS2322: Type 'string | undefined' is not assignable to type 'number | null'. | ||
!!! error TS2322: Type 'undefined' is not assignable to type 'number | null'. | ||
!!! error TS2322: Type 'T & U' is not assignable to type 'number'. | ||
let y5: number | undefined = x; // Error | ||
~~ | ||
!!! error TS2322: Type 'T & U' is not assignable to type 'number | undefined'. | ||
!!! error TS2322: Type 'string | undefined' is not assignable to type 'number | undefined'. | ||
!!! error TS2322: Type 'string' is not assignable to type 'number | undefined'. | ||
!!! error TS2322: Type 'T & U' is not assignable to type 'number'. | ||
let y6: null | undefined = x; // Error | ||
~~ | ||
!!! error TS2322: Type 'T & U' is not assignable to type 'null | undefined'. | ||
!!! error TS2322: Type 'string | undefined' is not assignable to type 'null | undefined'. | ||
!!! error TS2322: Type 'string' is not assignable to type 'null | undefined'. | ||
!!! error TS2322: Type 'T & U' is not assignable to type 'null'. | ||
} | ||
|
||
type T1 = (string | number | undefined) & (string | null | undefined); // string | undefined | ||
|
||
function f3<T extends string | number | undefined>(x: T & (number | object | undefined)) { | ||
const y: number | undefined = x; | ||
} | ||
|
||
function f4<T extends string | number>(x: T & (number | object)) { | ||
const y: number = x; | ||
} | ||
|
||
function f5<T, U extends keyof T>(x: keyof T & U) { | ||
let y: keyof any = x; | ||
} | ||
|
||
// Repro from #23648 | ||
|
||
type Example<T, U> = { [K in keyof T]: K extends keyof U ? UnexpectedError<K> : NoErrorHere<K> } | ||
|
||
type UnexpectedError<T extends PropertyKey> = T | ||
type NoErrorHere<T extends PropertyKey> = T | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
//// [intersectionWithUnionConstraint.ts] | ||
function f1<T extends string | number, U extends string | number>(x: T & U) { | ||
// Combined constraint of 'T & U' is 'string | number' | ||
let y: string | number = x; | ||
} | ||
|
||
function f2<T extends string | number | undefined, U extends string | null | undefined>(x: T & U) { | ||
let y1: string | number = x; // Error | ||
let y2: string | null = x; // Error | ||
let y3: string | undefined = x; | ||
let y4: number | null = x; // Error | ||
let y5: number | undefined = x; // Error | ||
let y6: null | undefined = x; // Error | ||
} | ||
|
||
type T1 = (string | number | undefined) & (string | null | undefined); // string | undefined | ||
|
||
function f3<T extends string | number | undefined>(x: T & (number | object | undefined)) { | ||
const y: number | undefined = x; | ||
} | ||
|
||
function f4<T extends string | number>(x: T & (number | object)) { | ||
const y: number = x; | ||
} | ||
|
||
function f5<T, U extends keyof T>(x: keyof T & U) { | ||
let y: keyof any = x; | ||
} | ||
|
||
// Repro from #23648 | ||
|
||
type Example<T, U> = { [K in keyof T]: K extends keyof U ? UnexpectedError<K> : NoErrorHere<K> } | ||
|
||
type UnexpectedError<T extends PropertyKey> = T | ||
type NoErrorHere<T extends PropertyKey> = T | ||
|
||
|
||
//// [intersectionWithUnionConstraint.js] | ||
"use strict"; | ||
function f1(x) { | ||
// Combined constraint of 'T & U' is 'string | number' | ||
var y = x; | ||
} | ||
function f2(x) { | ||
var y1 = x; // Error | ||
var y2 = x; // Error | ||
var y3 = x; | ||
var y4 = x; // Error | ||
var y5 = x; // Error | ||
var y6 = x; // Error | ||
} | ||
function f3(x) { | ||
var y = x; | ||
} | ||
function f4(x) { | ||
var y = x; | ||
} | ||
function f5(x) { | ||
var y = x; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahhh.... can we go with the term
UndefinedLike
instead? Sincevoid
is a TS-only and, conceptually, corresponds with a function return of no value, which maps to a runtime value ofundefined
(whereasundefined
doesn't really imply anythingvoid
-y on its own).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure. I went with void-like because
void
is the supertype, but either way works.