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

Reduce intersections by discriminants #36696

Merged
merged 35 commits into from
Feb 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
ace0904
Treat never-like intersections as never
ahejlsberg Feb 8, 2020
eaf872c
Accept new baselines
ahejlsberg Feb 8, 2020
94353d2
Fix compiler issues revealed by increased intersection correctness
ahejlsberg Feb 8, 2020
346ec30
Delete fourslash tests that are no longer applicable
ahejlsberg Feb 8, 2020
acd3fec
Include isNeverLikeIntersection check in getNormalizedType
ahejlsberg Feb 9, 2020
7ae4a21
Erase never-like types in several more places
ahejlsberg Feb 10, 2020
3a1ba48
Check that base types are not never-like
ahejlsberg Feb 10, 2020
e1434a5
Add comments
ahejlsberg Feb 10, 2020
37c6280
Revert isNeverLikeType check in getIndexType (keyof shouldn't resolve…
ahejlsberg Feb 10, 2020
dba1043
Introduce getReducedType for union and intersection types
ahejlsberg Feb 11, 2020
f170191
Don't reduce in getApparentType
ahejlsberg Feb 11, 2020
6b2f3a2
Avoid relationship check in resolveMappedTypeMembers
ahejlsberg Feb 12, 2020
aeab6af
Merge branch 'master' into neverLikeIntersections
ahejlsberg Feb 12, 2020
4f67910
Accept new baselines
ahejlsberg Feb 12, 2020
49302f2
Don't call getReducedType in getIndexType
ahejlsberg Feb 13, 2020
aa8d227
Ensure reduced and unreduced forms of a type can compare identical
ahejlsberg Feb 14, 2020
8907e92
Reduce types before converting them to string representation
ahejlsberg Feb 15, 2020
04facee
Accept new baselines
ahejlsberg Feb 15, 2020
88be5ea
Reduce intersections before obtaining keyof X
ahejlsberg Feb 17, 2020
fbf13dc
Add tests
ahejlsberg Feb 17, 2020
85447a5
Accept new baselines
ahejlsberg Feb 17, 2020
7741085
Fix comment in tests
ahejlsberg Feb 18, 2020
d2b3807
Don't infer from empty intersection types
ahejlsberg Feb 22, 2020
6590e20
Add tests
ahejlsberg Feb 22, 2020
40340ae
Accept new baselines
ahejlsberg Feb 22, 2020
c4c1418
Defer instantiation of mapped type property types
ahejlsberg Feb 24, 2020
8eb08e8
Accept new baselines
ahejlsberg Feb 24, 2020
874938c
Merge branch 'master' into neverLikeIntersections
ahejlsberg Feb 24, 2020
ede9d01
Include more precise type in diagnostic
ahejlsberg Feb 24, 2020
42022f3
Accept new baselines
ahejlsberg Feb 24, 2020
e24b250
Minor optimization
ahejlsberg Feb 25, 2020
12f6224
Merge branch 'master' into neverLikeIntersections
ahejlsberg Feb 25, 2020
a015ab9
Improve error message
ahejlsberg Feb 25, 2020
95ecb7b
Merge branch 'master' into neverLikeIntersections
ahejlsberg Feb 26, 2020
b99b9b2
Optional properties in intersections are never discriminants
ahejlsberg Feb 27, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 112 additions & 36 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -2361,6 +2361,10 @@
"category": "Error",
"code": 2614
},
"Type of property '{0}' circularly references itself in mapped type '{1}'.": {
"category": "Error",
"code": 2615
},

"Cannot augment module '{0}' with value exports because it resolves to a non-module entity.": {
"category": "Error",
Expand Down
4 changes: 1 addition & 3 deletions src/compiler/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1795,9 +1795,7 @@ namespace ts {

const target = getTargetOfBindingOrAssignmentElement(bindingElement);
if (target && isPropertyName(target)) {
return isComputedPropertyName(target) && isStringOrNumericLiteral(target.expression)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

? Related?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what your question is here? With the improved precision of intersections, the checker was proving that this expression could never be true.

? target.expression
: target;
return target;
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/compiler/transformers/classFields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -976,7 +976,7 @@ namespace ts {
);
}

function visitArrayAssignmentTarget(node: AssignmentPattern) {
function visitArrayAssignmentTarget(node: BindingOrAssignmentElement) {
const target = getTargetOfBindingOrAssignmentElement(node);
if (target && isPrivateIdentifierPropertyAccessExpression(target)) {
const wrapped = wrapPrivateIdentifierForDestructuringTarget(target);
Expand Down
25 changes: 21 additions & 4 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4136,6 +4136,9 @@ namespace ts {
OptionalParameter = 1 << 14, // Optional parameter
RestParameter = 1 << 15, // Rest parameter
DeferredType = 1 << 16, // Calculation of the type of this symbol is deferred due to processing costs, should be fetched with `getTypeOfSymbolWithDeferredType`
HasNeverType = 1 << 17, // Synthetic property with at least one never type in constituents
Mapped = 1 << 18, // Property of mapped type
StripOptional = 1 << 19, // Strip optionality in mapped property
Synthetic = SyntheticProperty | SyntheticMethod,
Discriminant = HasNonUniformType | HasLiteralType,
Partial = ReadPartial | WritePartial
Expand All @@ -4146,6 +4149,12 @@ namespace ts {
checkFlags: CheckFlags;
}

/* @internal */
export interface MappedSymbol extends TransientSymbol {
mappedType: MappedType;
mapper: TypeMapper;
}

/* @internal */
export interface ReverseMappedSymbol extends TransientSymbol {
propertyType: Type;
Expand Down Expand Up @@ -4343,16 +4352,16 @@ namespace ts {
NotPrimitiveUnion = Any | Unknown | Enum | Void | Never | StructuredOrInstantiable,
// The following flags are aggregated during union and intersection type construction
/* @internal */
IncludesMask = Any | Unknown | Primitive | Never | Object | Union | NonPrimitive,
IncludesMask = Any | Unknown | Primitive | Never | Object | Union | Intersection | NonPrimitive,
// The following flags are used for different purposes during union and intersection type construction
/* @internal */
IncludesStructuredOrInstantiable = TypeParameter,
/* @internal */
IncludesNonWideningType = Intersection,
IncludesNonWideningType = Index,
/* @internal */
IncludesWildcard = Index,
IncludesWildcard = IndexedAccess,
/* @internal */
IncludesEmptyObject = IndexedAccess,
IncludesEmptyObject = Conditional,
}

export type DestructuringPattern = BindingPattern | ObjectLiteralExpression | ArrayLiteralExpression;
Expand Down Expand Up @@ -4468,6 +4477,12 @@ namespace ts {
CouldContainTypeVariablesComputed = 1 << 26, // CouldContainTypeVariables flag has been computed
/* @internal */
CouldContainTypeVariables = 1 << 27, // Type could contain a type variable
/* @internal */
ContainsIntersections = 1 << 28, // Union contains intersections
/* @internal */
IsNeverIntersectionComputed = 1 << 28, // IsNeverLike flag has been computed
/* @internal */
IsNeverIntersection = 1 << 29, // Intersection reduces to never
ClassOrInterface = Class | Interface,
/* @internal */
RequiresWidening = ContainsWideningType | ContainsObjectOrArrayLiteral,
Expand Down Expand Up @@ -4589,6 +4604,8 @@ namespace ts {
}

export interface UnionType extends UnionOrIntersectionType {
/* @internal */
resolvedReducedType: Type;
}

export interface IntersectionType extends UnionOrIntersectionType {
Expand Down
7 changes: 2 additions & 5 deletions tests/baselines/reference/discriminatedUnionTypes2.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ tests/cases/conformance/types/union/discriminatedUnionTypes2.ts(27,30): error TS
Object literal may only specify known properties, and 'c' does not exist in type '{ a: null; b: string; }'.
tests/cases/conformance/types/union/discriminatedUnionTypes2.ts(32,11): error TS2339: Property 'b' does not exist on type '{ a: 0; b: string; } | { a: T; c: number; }'.
Property 'b' does not exist on type '{ a: T; c: number; }'.
tests/cases/conformance/types/union/discriminatedUnionTypes2.ts(132,11): error TS2339: Property 'value' does not exist on type 'never'.


==== tests/cases/conformance/types/union/discriminatedUnionTypes2.ts (3 errors) ====
==== tests/cases/conformance/types/union/discriminatedUnionTypes2.ts (2 errors) ====
function f10(x : { kind: false, a: string } | { kind: true, b: string } | { kind: string, c: string }) {
if (x.kind === false) {
x.a;
Expand Down Expand Up @@ -143,9 +142,7 @@ tests/cases/conformance/types/union/discriminatedUnionTypes2.ts(132,11): error T
x.value; // number
}
else {
x.value; // Error, x is never
~~~~~
!!! error TS2339: Property 'value' does not exist on type 'never'.
x.value; // number
}
}

Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/discriminatedUnionTypes2.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ function foo1(x: RuntimeValue & { type: 'number' }) {
x.value; // number
}
else {
x.value; // Error, x is never
x.value; // number
}
}

Expand Down Expand Up @@ -226,7 +226,7 @@ function foo1(x) {
x.value; // number
}
else {
x.value; // Error, x is never
x.value; // number
}
}
function foo2(x) {
Expand Down
16 changes: 9 additions & 7 deletions tests/baselines/reference/discriminatedUnionTypes2.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -317,9 +317,9 @@ function f(problem: abc & (b | c)) {
>c : Symbol(c, Decl(discriminatedUnionTypes2.ts, 104, 1))

if (problem.type === 'b') {
>problem.type : Symbol(type, Decl(discriminatedUnionTypes2.ts, 101, 10), Decl(discriminatedUnionTypes2.ts, 105, 10), Decl(discriminatedUnionTypes2.ts, 97, 10), Decl(discriminatedUnionTypes2.ts, 101, 10), Decl(discriminatedUnionTypes2.ts, 97, 10) ... and 5 more)
>problem.type : Symbol(type, Decl(discriminatedUnionTypes2.ts, 101, 10), Decl(discriminatedUnionTypes2.ts, 105, 10))
>problem : Symbol(problem, Decl(discriminatedUnionTypes2.ts, 112, 11))
>type : Symbol(type, Decl(discriminatedUnionTypes2.ts, 101, 10), Decl(discriminatedUnionTypes2.ts, 105, 10), Decl(discriminatedUnionTypes2.ts, 97, 10), Decl(discriminatedUnionTypes2.ts, 101, 10), Decl(discriminatedUnionTypes2.ts, 97, 10) ... and 5 more)
>type : Symbol(type, Decl(discriminatedUnionTypes2.ts, 101, 10), Decl(discriminatedUnionTypes2.ts, 105, 10))

problem.name;
>problem.name : Symbol(name, Decl(discriminatedUnionTypes2.ts, 102, 14))
Expand Down Expand Up @@ -356,18 +356,20 @@ function foo1(x: RuntimeValue & { type: 'number' }) {
>type : Symbol(type, Decl(discriminatedUnionTypes2.ts, 126, 33))

if (x.type === 'number') {
>x.type : Symbol(type, Decl(discriminatedUnionTypes2.ts, 122, 7), Decl(discriminatedUnionTypes2.ts, 126, 33), Decl(discriminatedUnionTypes2.ts, 123, 7), Decl(discriminatedUnionTypes2.ts, 126, 33), Decl(discriminatedUnionTypes2.ts, 124, 7) ... and 1 more)
>x.type : Symbol(type, Decl(discriminatedUnionTypes2.ts, 122, 7), Decl(discriminatedUnionTypes2.ts, 126, 33))
>x : Symbol(x, Decl(discriminatedUnionTypes2.ts, 126, 14))
>type : Symbol(type, Decl(discriminatedUnionTypes2.ts, 122, 7), Decl(discriminatedUnionTypes2.ts, 126, 33), Decl(discriminatedUnionTypes2.ts, 123, 7), Decl(discriminatedUnionTypes2.ts, 126, 33), Decl(discriminatedUnionTypes2.ts, 124, 7) ... and 1 more)
>type : Symbol(type, Decl(discriminatedUnionTypes2.ts, 122, 7), Decl(discriminatedUnionTypes2.ts, 126, 33))

x.value; // number
>x.value : Symbol(value, Decl(discriminatedUnionTypes2.ts, 122, 23))
>x : Symbol(x, Decl(discriminatedUnionTypes2.ts, 126, 14))
>value : Symbol(value, Decl(discriminatedUnionTypes2.ts, 122, 23))
}
else {
x.value; // Error, x is never
x.value; // number
>x.value : Symbol(value, Decl(discriminatedUnionTypes2.ts, 122, 23))
>x : Symbol(x, Decl(discriminatedUnionTypes2.ts, 126, 14))
>value : Symbol(value, Decl(discriminatedUnionTypes2.ts, 122, 23))
}
}

Expand All @@ -379,9 +381,9 @@ function foo2(x: RuntimeValue & ({ type: 'number' } | { type: 'string' })) {
>type : Symbol(type, Decl(discriminatedUnionTypes2.ts, 135, 55))

if (x.type === 'number') {
>x.type : Symbol(type, Decl(discriminatedUnionTypes2.ts, 122, 7), Decl(discriminatedUnionTypes2.ts, 135, 34), Decl(discriminatedUnionTypes2.ts, 122, 7), Decl(discriminatedUnionTypes2.ts, 135, 55), Decl(discriminatedUnionTypes2.ts, 123, 7) ... and 7 more)
>x.type : Symbol(type, Decl(discriminatedUnionTypes2.ts, 122, 7), Decl(discriminatedUnionTypes2.ts, 135, 34), Decl(discriminatedUnionTypes2.ts, 123, 7), Decl(discriminatedUnionTypes2.ts, 135, 55))
>x : Symbol(x, Decl(discriminatedUnionTypes2.ts, 135, 14))
>type : Symbol(type, Decl(discriminatedUnionTypes2.ts, 122, 7), Decl(discriminatedUnionTypes2.ts, 135, 34), Decl(discriminatedUnionTypes2.ts, 122, 7), Decl(discriminatedUnionTypes2.ts, 135, 55), Decl(discriminatedUnionTypes2.ts, 123, 7) ... and 7 more)
>type : Symbol(type, Decl(discriminatedUnionTypes2.ts, 122, 7), Decl(discriminatedUnionTypes2.ts, 135, 34), Decl(discriminatedUnionTypes2.ts, 123, 7), Decl(discriminatedUnionTypes2.ts, 135, 55))

x.value; // number
>x.value : Symbol(value, Decl(discriminatedUnionTypes2.ts, 122, 23))
Expand Down
26 changes: 13 additions & 13 deletions tests/baselines/reference/discriminatedUnionTypes2.types
Original file line number Diff line number Diff line change
Expand Up @@ -323,13 +323,13 @@ type abc = a | b | c;
>abc : abc

function f(problem: abc & (b | c)) {
>f : (problem: b | c | (a & b) | (a & c) | (b & c) | (c & b)) => void
>problem : b | c | (a & b) | (a & c) | (b & c) | (c & b)
>f : (problem: b | c) => void
>problem : b | c

if (problem.type === 'b') {
>problem.type === 'b' : boolean
>problem.type : "b" | "c"
>problem : b | c | (a & b) | (a & c) | (b & c) | (c & b)
>problem : b | c
>type : "b" | "c"
>'b' : "b"

Expand Down Expand Up @@ -362,14 +362,14 @@ type RuntimeValue =
>value : boolean

function foo1(x: RuntimeValue & { type: 'number' }) {
>foo1 : (x: ({ type: "number"; value: number; } & { type: "number"; }) | ({ type: "string"; value: string; } & { type: "number"; }) | ({ type: "boolean"; value: boolean; } & { type: "number"; })) => void
>x : ({ type: "number"; value: number; } & { type: "number"; }) | ({ type: "string"; value: string; } & { type: "number"; }) | ({ type: "boolean"; value: boolean; } & { type: "number"; })
>foo1 : (x: { type: "number"; value: number; } & { type: "number"; }) => void
>x : { type: "number"; value: number; } & { type: "number"; }
>type : "number"

if (x.type === 'number') {
>x.type === 'number' : boolean
>x.type : "number"
>x : ({ type: "number"; value: number; } & { type: "number"; }) | ({ type: "string"; value: string; } & { type: "number"; }) | ({ type: "boolean"; value: boolean; } & { type: "number"; })
>x : { type: "number"; value: number; } & { type: "number"; }
>type : "number"
>'number' : "number"

Expand All @@ -379,23 +379,23 @@ function foo1(x: RuntimeValue & { type: 'number' }) {
>value : number
}
else {
x.value; // Error, x is never
>x.value : any
>x : never
>value : any
x.value; // number
>x.value : number
>x : { type: "number"; value: number; } & { type: "number"; }
>value : number
}
}

function foo2(x: RuntimeValue & ({ type: 'number' } | { type: 'string' })) {
>foo2 : (x: ({ type: "number"; value: number; } & { type: "number"; }) | ({ type: "number"; value: number; } & { type: "string"; }) | ({ type: "string"; value: string; } & { type: "number"; }) | ({ type: "string"; value: string; } & { type: "string"; }) | ({ type: "boolean"; value: boolean; } & { type: "number"; }) | ({ type: "boolean"; value: boolean; } & { type: "string"; })) => void
>x : ({ type: "number"; value: number; } & { type: "number"; }) | ({ type: "number"; value: number; } & { type: "string"; }) | ({ type: "string"; value: string; } & { type: "number"; }) | ({ type: "string"; value: string; } & { type: "string"; }) | ({ type: "boolean"; value: boolean; } & { type: "number"; }) | ({ type: "boolean"; value: boolean; } & { type: "string"; })
>foo2 : (x: ({ type: "number"; value: number; } & { type: "number"; }) | ({ type: "string"; value: string; } & { type: "string"; })) => void
>x : ({ type: "number"; value: number; } & { type: "number"; }) | ({ type: "string"; value: string; } & { type: "string"; })
>type : "number"
>type : "string"

if (x.type === 'number') {
>x.type === 'number' : boolean
>x.type : "string" | "number"
>x : ({ type: "number"; value: number; } & { type: "number"; }) | ({ type: "number"; value: number; } & { type: "string"; }) | ({ type: "string"; value: string; } & { type: "number"; }) | ({ type: "string"; value: string; } & { type: "string"; }) | ({ type: "boolean"; value: boolean; } & { type: "number"; }) | ({ type: "boolean"; value: boolean; } & { type: "string"; })
>x : ({ type: "number"; value: number; } & { type: "number"; }) | ({ type: "string"; value: string; } & { type: "string"; })
>type : "string" | "number"
>'number' : "number"

Expand Down
61 changes: 58 additions & 3 deletions tests/baselines/reference/intersectionReduction.errors.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
tests/cases/conformance/types/intersection/intersectionReduction.ts(40,1): error TS2322: Type 'any' is not assignable to type 'never'.
tests/cases/conformance/types/intersection/intersectionReduction.ts(41,1): error TS2322: Type 'any' is not assignable to type 'never'.
tests/cases/conformance/types/intersection/intersectionReduction.ts(38,4): error TS2339: Property 'kind' does not exist on type 'never'.
tests/cases/conformance/types/intersection/intersectionReduction.ts(80,1): error TS2322: Type 'any' is not assignable to type 'never'.
tests/cases/conformance/types/intersection/intersectionReduction.ts(81,1): error TS2322: Type 'any' is not assignable to type 'never'.


==== tests/cases/conformance/types/intersection/intersectionReduction.ts (2 errors) ====
==== tests/cases/conformance/types/intersection/intersectionReduction.ts (3 errors) ====
declare const sym1: unique symbol;
declare const sym2: unique symbol;

Expand Down Expand Up @@ -35,6 +36,48 @@ tests/cases/conformance/types/intersection/intersectionReduction.ts(41,1): error
type X6 = X | symbol & string;
type X7 = X | void & string;

type A = { kind: 'a', foo: string };
type B = { kind: 'b', foo: number };
type C = { kind: 'c', foo: number };

declare let ab: A & B;
ab.kind; // Error
~~~~
!!! error TS2339: Property 'kind' does not exist on type 'never'.

declare let x: A | (B & C); // A
let a: A = x;

type AB = A & B; // never
type BC = B & C; // never

type U1 = Partial<A & B>; // never
type U2 = Readonly<A & B>; // never
type U3 = (A & B)['kind']; // never
type U4 = A & B | B & C; // never
type U5 = A | B & C; // A

type K1 = keyof (A & B); // string | number | symbol
type K2 = keyof A | keyof B; // 'kind' | 'foo'

type Merge1<T, U> = { [P in keyof (T & U)]: P extends keyof T ? T[P] : U[P & keyof U] }
type Merge2<T, U> = { [P in keyof T | keyof U]: P extends keyof T ? T[P] : U[P & keyof U] }

type M1 = { a: 1, b: 2 } & { a: 2, c: 3 }; // never
type M2 = Merge1<{ a: 1, b: 2 }, { a: 2, c: 3 }>; // {}
type M3 = Merge2<{ a: 1, b: 2 }, { a: 2, c: 3 }>; // { a: 1, b: 2, c: 3 }

type D = { kind: 'd', foo: unknown };
type E = { kind: 'e', foo: unknown };

declare function f10<T>(x: { foo: T }): T;

declare let a1: A | D;
declare let a2: A | D & E;

let r1 = f10(a1); // unknown
let r2 = f10(a2); // string

// Repro from #31663

const x1 = { a: 'foo', b: 42 };
Expand Down Expand Up @@ -63,4 +106,16 @@ tests/cases/conformance/types/intersection/intersectionReduction.ts(41,1): error

t1 = t2;
t2 = t1;

// Repro from #36736

const f1 = (t: "a" | ("b" & "c")): "a" => t;

type Container<Type extends string> = {
type: Type;
}

const f2 = (t: Container<"a"> | (Container<"b"> & Container<"c">)): Container<"a"> => t;
const f3 = (t: Container<"a"> | (Container<"b"> & { dataB: boolean } & Container<"a">)): Container<"a"> => t;
const f4 = (t: number | (Container<"b"> & { dataB: boolean } & Container<"a">)): number => t;

Loading