Fix assignment of intersections to objects with optional properties (#37195)

* Treat intersections of only objects as a single object in relations

* Exclude intersections containing non-inferrable types

* Accept new baselines

* Update test

* Accept new baselines

* Add tests
This commit is contained in:
Anders Hejlsberg 2020-03-14 09:45:05 -07:00 committed by GitHub
parent ad8d3d90a5
commit b8baf48043
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 265 additions and 31 deletions

View file

@ -15415,7 +15415,9 @@ namespace ts {
//
// - For a primitive type or type parameter (such as 'number = A & B') there is no point in
// breaking the intersection apart.
result = someTypeRelatedToType(<IntersectionType>source, target, /*reportErrors*/ false, IntersectionState.Source);
if (!isNonGenericObjectType(target) || !every((<IntersectionType>source).types, t => isNonGenericObjectType(t) && !(getObjectFlags(t) & ObjectFlags.NonInferrableType))) {
result = someTypeRelatedToType(<IntersectionType>source, target, /*reportErrors*/ false, IntersectionState.Source);
}
}
if (!result && (source.flags & TypeFlags.StructuredOrInstantiable || target.flags & TypeFlags.StructuredOrInstantiable)) {
if (result = recursiveTypeRelatedTo(source, target, reportErrors, intersectionState)) {

View file

@ -0,0 +1,49 @@
tests/cases/compiler/intersectionsAndOptionalProperties.ts(5,1): error TS2322: Type '{ a: null; b: string; }' is not assignable to type '{ a?: number | undefined; b: string; }'.
Types of property 'a' are incompatible.
Type 'null' is not assignable to type 'number | undefined'.
tests/cases/compiler/intersectionsAndOptionalProperties.ts(6,1): error TS2322: Type '{ a: null; } & { b: string; }' is not assignable to type '{ a?: number | undefined; b: string; }'.
Types of property 'a' are incompatible.
Type 'null' is not assignable to type 'number | undefined'.
tests/cases/compiler/intersectionsAndOptionalProperties.ts(19,5): error TS2322: Type 'From' is not assignable to type 'To'.
Types of property 'field' are incompatible.
Type 'null' is not assignable to type 'number | undefined'.
tests/cases/compiler/intersectionsAndOptionalProperties.ts(20,5): error TS2322: Type 'null' is not assignable to type 'number | undefined'.
==== tests/cases/compiler/intersectionsAndOptionalProperties.ts (4 errors) ====
declare let x: { a?: number, b: string };
declare let y: { a: null, b: string };
declare let z: { a: null } & { b: string };
x = y; // Error
~
!!! error TS2322: Type '{ a: null; b: string; }' is not assignable to type '{ a?: number | undefined; b: string; }'.
!!! error TS2322: Types of property 'a' are incompatible.
!!! error TS2322: Type 'null' is not assignable to type 'number | undefined'.
x = z; // Error
~
!!! error TS2322: Type '{ a: null; } & { b: string; }' is not assignable to type '{ a?: number | undefined; b: string; }'.
!!! error TS2322: Types of property 'a' are incompatible.
!!! error TS2322: Type 'null' is not assignable to type 'number | undefined'.
// Repro from #36604
interface To {
field?: number;
anotherField: string;
}
type From = { field: null } & Omit<To, 'field'>;
function foo(v: From) {
let x: To;
x = v; // Error
~
!!! error TS2322: Type 'From' is not assignable to type 'To'.
!!! error TS2322: Types of property 'field' are incompatible.
!!! error TS2322: Type 'null' is not assignable to type 'number | undefined'.
x.field = v.field; // Error
~~~~~~~
!!! error TS2322: Type 'null' is not assignable to type 'number | undefined'.
}

View file

@ -0,0 +1,33 @@
//// [intersectionsAndOptionalProperties.ts]
declare let x: { a?: number, b: string };
declare let y: { a: null, b: string };
declare let z: { a: null } & { b: string };
x = y; // Error
x = z; // Error
// Repro from #36604
interface To {
field?: number;
anotherField: string;
}
type From = { field: null } & Omit<To, 'field'>;
function foo(v: From) {
let x: To;
x = v; // Error
x.field = v.field; // Error
}
//// [intersectionsAndOptionalProperties.js]
"use strict";
x = y; // Error
x = z; // Error
function foo(v) {
var x;
x = v; // Error
x.field = v.field; // Error
}

View file

@ -0,0 +1,64 @@
=== tests/cases/compiler/intersectionsAndOptionalProperties.ts ===
declare let x: { a?: number, b: string };
>x : Symbol(x, Decl(intersectionsAndOptionalProperties.ts, 0, 11))
>a : Symbol(a, Decl(intersectionsAndOptionalProperties.ts, 0, 16))
>b : Symbol(b, Decl(intersectionsAndOptionalProperties.ts, 0, 28))
declare let y: { a: null, b: string };
>y : Symbol(y, Decl(intersectionsAndOptionalProperties.ts, 1, 11))
>a : Symbol(a, Decl(intersectionsAndOptionalProperties.ts, 1, 16))
>b : Symbol(b, Decl(intersectionsAndOptionalProperties.ts, 1, 25))
declare let z: { a: null } & { b: string };
>z : Symbol(z, Decl(intersectionsAndOptionalProperties.ts, 2, 11))
>a : Symbol(a, Decl(intersectionsAndOptionalProperties.ts, 2, 16))
>b : Symbol(b, Decl(intersectionsAndOptionalProperties.ts, 2, 30))
x = y; // Error
>x : Symbol(x, Decl(intersectionsAndOptionalProperties.ts, 0, 11))
>y : Symbol(y, Decl(intersectionsAndOptionalProperties.ts, 1, 11))
x = z; // Error
>x : Symbol(x, Decl(intersectionsAndOptionalProperties.ts, 0, 11))
>z : Symbol(z, Decl(intersectionsAndOptionalProperties.ts, 2, 11))
// Repro from #36604
interface To {
>To : Symbol(To, Decl(intersectionsAndOptionalProperties.ts, 5, 6))
field?: number;
>field : Symbol(To.field, Decl(intersectionsAndOptionalProperties.ts, 9, 14))
anotherField: string;
>anotherField : Symbol(To.anotherField, Decl(intersectionsAndOptionalProperties.ts, 10, 19))
}
type From = { field: null } & Omit<To, 'field'>;
>From : Symbol(From, Decl(intersectionsAndOptionalProperties.ts, 12, 1))
>field : Symbol(field, Decl(intersectionsAndOptionalProperties.ts, 14, 14))
>Omit : Symbol(Omit, Decl(lib.es5.d.ts, --, --))
>To : Symbol(To, Decl(intersectionsAndOptionalProperties.ts, 5, 6))
function foo(v: From) {
>foo : Symbol(foo, Decl(intersectionsAndOptionalProperties.ts, 14, 49))
>v : Symbol(v, Decl(intersectionsAndOptionalProperties.ts, 16, 13))
>From : Symbol(From, Decl(intersectionsAndOptionalProperties.ts, 12, 1))
let x: To;
>x : Symbol(x, Decl(intersectionsAndOptionalProperties.ts, 17, 7))
>To : Symbol(To, Decl(intersectionsAndOptionalProperties.ts, 5, 6))
x = v; // Error
>x : Symbol(x, Decl(intersectionsAndOptionalProperties.ts, 17, 7))
>v : Symbol(v, Decl(intersectionsAndOptionalProperties.ts, 16, 13))
x.field = v.field; // Error
>x.field : Symbol(To.field, Decl(intersectionsAndOptionalProperties.ts, 9, 14))
>x : Symbol(x, Decl(intersectionsAndOptionalProperties.ts, 17, 7))
>field : Symbol(To.field, Decl(intersectionsAndOptionalProperties.ts, 9, 14))
>v.field : Symbol(field, Decl(intersectionsAndOptionalProperties.ts, 14, 14))
>v : Symbol(v, Decl(intersectionsAndOptionalProperties.ts, 16, 13))
>field : Symbol(field, Decl(intersectionsAndOptionalProperties.ts, 14, 14))
}

View file

@ -0,0 +1,65 @@
=== tests/cases/compiler/intersectionsAndOptionalProperties.ts ===
declare let x: { a?: number, b: string };
>x : { a?: number | undefined; b: string; }
>a : number | undefined
>b : string
declare let y: { a: null, b: string };
>y : { a: null; b: string; }
>a : null
>null : null
>b : string
declare let z: { a: null } & { b: string };
>z : { a: null; } & { b: string; }
>a : null
>null : null
>b : string
x = y; // Error
>x = y : { a: null; b: string; }
>x : { a?: number | undefined; b: string; }
>y : { a: null; b: string; }
x = z; // Error
>x = z : { a: null; } & { b: string; }
>x : { a?: number | undefined; b: string; }
>z : { a: null; } & { b: string; }
// Repro from #36604
interface To {
field?: number;
>field : number | undefined
anotherField: string;
>anotherField : string
}
type From = { field: null } & Omit<To, 'field'>;
>From : From
>field : null
>null : null
function foo(v: From) {
>foo : (v: From) => void
>v : From
let x: To;
>x : To
x = v; // Error
>x = v : From
>x : To
>v : From
x.field = v.field; // Error
>x.field = v.field : null
>x.field : number | undefined
>x : To
>field : number | undefined
>v.field : null
>v : From
>field : null
}

View file

@ -1,5 +1,5 @@
tests/cases/compiler/normalizedIntersectionTooComplex.ts(36,14): error TS2590: Expression produces a union type that is too complex to represent.
tests/cases/compiler/normalizedIntersectionTooComplex.ts(36,40): error TS7006: Parameter 'x' implicitly has an 'any' type.
tests/cases/compiler/normalizedIntersectionTooComplex.ts(36,40): error TS2590: Expression produces a union type that is too complex to represent.
==== tests/cases/compiler/normalizedIntersectionTooComplex.ts (2 errors) ====
@ -39,8 +39,8 @@ tests/cases/compiler/normalizedIntersectionTooComplex.ts(36,40): error TS2590: E
declare var all: keyof Big;
const ctor = getCtor(all);
const comp = ctor({ common: "ok", ref: x => console.log(x) });
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2590: Expression produces a union type that is too complex to represent.
~
!!! error TS7006: Parameter 'x' implicitly has an 'any' type.
~~~~~~~~~~~~~~~~~~~
!!! error TS2590: Expression produces a union type that is too complex to represent.

View file

@ -14,7 +14,7 @@ export interface Validator<T> {
[nominalTypeHack]?: T;
}
export interface Requireable<T> extends Validator<T | undefined | null> {
export interface Requireable<T> extends Validator<T> {
isRequired: Validator<NonNullable<T>>;
}

View file

@ -59,14 +59,14 @@ export interface Validator<T> {
>T : Symbol(T, Decl(index.d.ts, 8, 27))
}
export interface Requireable<T> extends Validator<T | undefined | null> {
export interface Requireable<T> extends Validator<T> {
>Requireable : Symbol(Requireable, Decl(index.d.ts, 11, 1))
>T : Symbol(T, Decl(index.d.ts, 13, 29))
>Validator : Symbol(Validator, Decl(index.d.ts, 6, 72))
>T : Symbol(T, Decl(index.d.ts, 13, 29))
isRequired: Validator<NonNullable<T>>;
>isRequired : Symbol(Requireable.isRequired, Decl(index.d.ts, 13, 73))
>isRequired : Symbol(Requireable.isRequired, Decl(index.d.ts, 13, 54))
>Validator : Symbol(Validator, Decl(index.d.ts, 6, 72))
>NonNullable : Symbol(NonNullable, Decl(lib.es5.d.ts, --, --))
>T : Symbol(T, Decl(index.d.ts, 13, 29))
@ -202,11 +202,11 @@ const innerProps = {
foo: PropTypes.string.isRequired,
>foo : Symbol(foo, Decl(file.ts, 18, 20))
>PropTypes.string.isRequired : Symbol(PropTypes.Requireable.isRequired, Decl(index.d.ts, 13, 73))
>PropTypes.string.isRequired : Symbol(PropTypes.Requireable.isRequired, Decl(index.d.ts, 13, 54))
>PropTypes.string : Symbol(PropTypes.string, Decl(index.d.ts, 27, 12))
>PropTypes : Symbol(PropTypes, Decl(file.ts, 0, 6))
>string : Symbol(PropTypes.string, Decl(index.d.ts, 27, 12))
>isRequired : Symbol(PropTypes.Requireable.isRequired, Decl(index.d.ts, 13, 73))
>isRequired : Symbol(PropTypes.Requireable.isRequired, Decl(index.d.ts, 13, 54))
bar: PropTypes.bool,
>bar : Symbol(bar, Decl(file.ts, 19, 37))
@ -242,11 +242,11 @@ const arrayOfTypes = [PropTypes.string, PropTypes.bool, PropTypes.shape({
bar: PropTypes.number.isRequired
>bar : Symbol(bar, Decl(file.ts, 25, 26))
>PropTypes.number.isRequired : Symbol(PropTypes.Requireable.isRequired, Decl(index.d.ts, 13, 73))
>PropTypes.number.isRequired : Symbol(PropTypes.Requireable.isRequired, Decl(index.d.ts, 13, 54))
>PropTypes.number : Symbol(PropTypes.number, Decl(index.d.ts, 28, 12))
>PropTypes : Symbol(PropTypes, Decl(file.ts, 0, 6))
>number : Symbol(PropTypes.number, Decl(index.d.ts, 28, 12))
>isRequired : Symbol(PropTypes.Requireable.isRequired, Decl(index.d.ts, 13, 73))
>isRequired : Symbol(PropTypes.Requireable.isRequired, Decl(index.d.ts, 13, 54))
})];
@ -263,37 +263,37 @@ const propTypes: PropTypesMap = {
array: PropTypes.array.isRequired,
>array : Symbol(array, Decl(file.ts, 31, 23))
>PropTypes.array.isRequired : Symbol(PropTypes.Requireable.isRequired, Decl(index.d.ts, 13, 73))
>PropTypes.array.isRequired : Symbol(PropTypes.Requireable.isRequired, Decl(index.d.ts, 13, 54))
>PropTypes.array : Symbol(PropTypes.array, Decl(index.d.ts, 25, 12))
>PropTypes : Symbol(PropTypes, Decl(file.ts, 0, 6))
>array : Symbol(PropTypes.array, Decl(index.d.ts, 25, 12))
>isRequired : Symbol(PropTypes.Requireable.isRequired, Decl(index.d.ts, 13, 73))
>isRequired : Symbol(PropTypes.Requireable.isRequired, Decl(index.d.ts, 13, 54))
bool: PropTypes.bool.isRequired,
>bool : Symbol(bool, Decl(file.ts, 32, 38))
>PropTypes.bool.isRequired : Symbol(PropTypes.Requireable.isRequired, Decl(index.d.ts, 13, 73))
>PropTypes.bool.isRequired : Symbol(PropTypes.Requireable.isRequired, Decl(index.d.ts, 13, 54))
>PropTypes.bool : Symbol(PropTypes.bool, Decl(index.d.ts, 26, 12))
>PropTypes : Symbol(PropTypes, Decl(file.ts, 0, 6))
>bool : Symbol(PropTypes.bool, Decl(index.d.ts, 26, 12))
>isRequired : Symbol(PropTypes.Requireable.isRequired, Decl(index.d.ts, 13, 73))
>isRequired : Symbol(PropTypes.Requireable.isRequired, Decl(index.d.ts, 13, 54))
shape: PropTypes.shape(innerProps).isRequired,
>shape : Symbol(shape, Decl(file.ts, 33, 36))
>PropTypes.shape(innerProps).isRequired : Symbol(PropTypes.Requireable.isRequired, Decl(index.d.ts, 13, 73))
>PropTypes.shape(innerProps).isRequired : Symbol(PropTypes.Requireable.isRequired, Decl(index.d.ts, 13, 54))
>PropTypes.shape : Symbol(PropTypes.shape, Decl(index.d.ts, 28, 41))
>PropTypes : Symbol(PropTypes, Decl(file.ts, 0, 6))
>shape : Symbol(PropTypes.shape, Decl(index.d.ts, 28, 41))
>innerProps : Symbol(innerProps, Decl(file.ts, 18, 5))
>isRequired : Symbol(PropTypes.Requireable.isRequired, Decl(index.d.ts, 13, 73))
>isRequired : Symbol(PropTypes.Requireable.isRequired, Decl(index.d.ts, 13, 54))
oneOfType: PropTypes.oneOfType(arrayOfTypes).isRequired,
>oneOfType : Symbol(oneOfType, Decl(file.ts, 34, 50))
>PropTypes.oneOfType(arrayOfTypes).isRequired : Symbol(PropTypes.Requireable.isRequired, Decl(index.d.ts, 13, 73))
>PropTypes.oneOfType(arrayOfTypes).isRequired : Symbol(PropTypes.Requireable.isRequired, Decl(index.d.ts, 13, 54))
>PropTypes.oneOfType : Symbol(PropTypes.oneOfType, Decl(index.d.ts, 29, 89))
>PropTypes : Symbol(PropTypes, Decl(file.ts, 0, 6))
>oneOfType : Symbol(PropTypes.oneOfType, Decl(index.d.ts, 29, 89))
>arrayOfTypes : Symbol(arrayOfTypes, Decl(file.ts, 24, 5))
>isRequired : Symbol(PropTypes.Requireable.isRequired, Decl(index.d.ts, 13, 73))
>isRequired : Symbol(PropTypes.Requireable.isRequired, Decl(index.d.ts, 13, 54))
};
@ -309,37 +309,37 @@ const propTypesWithoutAnnotation = {
array: PropTypes.array.isRequired,
>array : Symbol(array, Decl(file.ts, 40, 23))
>PropTypes.array.isRequired : Symbol(PropTypes.Requireable.isRequired, Decl(index.d.ts, 13, 73))
>PropTypes.array.isRequired : Symbol(PropTypes.Requireable.isRequired, Decl(index.d.ts, 13, 54))
>PropTypes.array : Symbol(PropTypes.array, Decl(index.d.ts, 25, 12))
>PropTypes : Symbol(PropTypes, Decl(file.ts, 0, 6))
>array : Symbol(PropTypes.array, Decl(index.d.ts, 25, 12))
>isRequired : Symbol(PropTypes.Requireable.isRequired, Decl(index.d.ts, 13, 73))
>isRequired : Symbol(PropTypes.Requireable.isRequired, Decl(index.d.ts, 13, 54))
bool: PropTypes.bool.isRequired,
>bool : Symbol(bool, Decl(file.ts, 41, 38))
>PropTypes.bool.isRequired : Symbol(PropTypes.Requireable.isRequired, Decl(index.d.ts, 13, 73))
>PropTypes.bool.isRequired : Symbol(PropTypes.Requireable.isRequired, Decl(index.d.ts, 13, 54))
>PropTypes.bool : Symbol(PropTypes.bool, Decl(index.d.ts, 26, 12))
>PropTypes : Symbol(PropTypes, Decl(file.ts, 0, 6))
>bool : Symbol(PropTypes.bool, Decl(index.d.ts, 26, 12))
>isRequired : Symbol(PropTypes.Requireable.isRequired, Decl(index.d.ts, 13, 73))
>isRequired : Symbol(PropTypes.Requireable.isRequired, Decl(index.d.ts, 13, 54))
shape: PropTypes.shape(innerProps).isRequired,
>shape : Symbol(shape, Decl(file.ts, 42, 36))
>PropTypes.shape(innerProps).isRequired : Symbol(PropTypes.Requireable.isRequired, Decl(index.d.ts, 13, 73))
>PropTypes.shape(innerProps).isRequired : Symbol(PropTypes.Requireable.isRequired, Decl(index.d.ts, 13, 54))
>PropTypes.shape : Symbol(PropTypes.shape, Decl(index.d.ts, 28, 41))
>PropTypes : Symbol(PropTypes, Decl(file.ts, 0, 6))
>shape : Symbol(PropTypes.shape, Decl(index.d.ts, 28, 41))
>innerProps : Symbol(innerProps, Decl(file.ts, 18, 5))
>isRequired : Symbol(PropTypes.Requireable.isRequired, Decl(index.d.ts, 13, 73))
>isRequired : Symbol(PropTypes.Requireable.isRequired, Decl(index.d.ts, 13, 54))
oneOfType: PropTypes.oneOfType(arrayOfTypes).isRequired,
>oneOfType : Symbol(oneOfType, Decl(file.ts, 43, 50))
>PropTypes.oneOfType(arrayOfTypes).isRequired : Symbol(PropTypes.Requireable.isRequired, Decl(index.d.ts, 13, 73))
>PropTypes.oneOfType(arrayOfTypes).isRequired : Symbol(PropTypes.Requireable.isRequired, Decl(index.d.ts, 13, 54))
>PropTypes.oneOfType : Symbol(PropTypes.oneOfType, Decl(index.d.ts, 29, 89))
>PropTypes : Symbol(PropTypes, Decl(file.ts, 0, 6))
>oneOfType : Symbol(PropTypes.oneOfType, Decl(index.d.ts, 29, 89))
>arrayOfTypes : Symbol(arrayOfTypes, Decl(file.ts, 24, 5))
>isRequired : Symbol(PropTypes.Requireable.isRequired, Decl(index.d.ts, 13, 73))
>isRequired : Symbol(PropTypes.Requireable.isRequired, Decl(index.d.ts, 13, 54))
};

View file

@ -35,9 +35,7 @@ export interface Validator<T> {
>nominalTypeHack : unique symbol
}
export interface Requireable<T> extends Validator<T | undefined | null> {
>null : null
export interface Requireable<T> extends Validator<T> {
isRequired: Validator<NonNullable<T>>;
>isRequired : Validator<NonNullable<T>>
}

View file

@ -0,0 +1,23 @@
// @strict: true
declare let x: { a?: number, b: string };
declare let y: { a: null, b: string };
declare let z: { a: null } & { b: string };
x = y; // Error
x = z; // Error
// Repro from #36604
interface To {
field?: number;
anotherField: string;
}
type From = { field: null } & Omit<To, 'field'>;
function foo(v: From) {
let x: To;
x = v; // Error
x.field = v.field; // Error
}

View file

@ -13,7 +13,7 @@ export interface Validator<T> {
[nominalTypeHack]?: T;
}
export interface Requireable<T> extends Validator<T | undefined | null> {
export interface Requireable<T> extends Validator<T> {
isRequired: Validator<NonNullable<T>>;
}