diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index eb23835ffc4a9..d6bf84fd9a989 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -10631,6 +10631,23 @@ namespace ts { target = getSimplifiedType(target); } + // Try to see if we're relating something like `Foo` -> `Bar | null | undefined`. + // If so, reporting the `null` and `undefined` in the type is hardly useful. + // First, see if we're even relating an object type to a union. + // Then see if the target is stripped down to a single non-union type. + // Note + // * We actually want to remove null and undefined naively here (rather than using getNonNullableType), + // since we don't want to end up with a worse error like "`Foo` is not assignable to `NonNullable`" + // when dealing with generics. + // * We also don't deal with primitive source types, since we already halt elaboration below. + if (target.flags & TypeFlags.Union && source.flags & TypeFlags.Object && + (target as UnionType).types.length <= 3 && maybeTypeOfKind(target, TypeFlags.Nullable)) { + const nullStrippedTarget = extractTypesOfKind(target, ~TypeFlags.Nullable); + if (!(nullStrippedTarget.flags & (TypeFlags.Union | TypeFlags.Never))) { + target = nullStrippedTarget; + } + } + // both types are the same - covers 'they are the same primitive type or both are Any' or the same type parameter cases if (source === target) return Ternary.True; @@ -12223,7 +12240,7 @@ namespace ts { if (deferredGlobalNonNullableTypeAlias !== unknownSymbol) { return getTypeAliasInstantiation(deferredGlobalNonNullableTypeAlias, [type]); } - return getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); // Type alias unavailable, fall back to non-higherorder behavior + return getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); // Type alias unavailable, fall back to non-higher-order behavior } function getNonNullableType(type: Type): Type { diff --git a/tests/baselines/reference/elaboratedErrorsOnNullableTargets01.errors.txt b/tests/baselines/reference/elaboratedErrorsOnNullableTargets01.errors.txt new file mode 100644 index 0000000000000..e2a3ebbf1db72 --- /dev/null +++ b/tests/baselines/reference/elaboratedErrorsOnNullableTargets01.errors.txt @@ -0,0 +1,31 @@ +tests/cases/compiler/elaboratedErrorsOnNullableTargets01.ts(4,1): error TS2322: Type '{ foo: { bar: number; }; }' is not assignable to type '{ foo: { bar: string; }; }'. + Types of property 'foo' are incompatible. + Type '{ bar: number; }' is not assignable to type '{ bar: string; }'. + Types of property 'bar' are incompatible. + Type 'number' is not assignable to type 'string'. +tests/cases/compiler/elaboratedErrorsOnNullableTargets01.ts(6,1): error TS2322: Type '{ foo: { bar: string; }; }' is not assignable to type '{ foo: { bar: number; }; }'. + Types of property 'foo' are incompatible. + Type '{ bar: string; }' is not assignable to type '{ bar: number; }'. + Types of property 'bar' are incompatible. + Type 'string' is not assignable to type 'number'. + + +==== tests/cases/compiler/elaboratedErrorsOnNullableTargets01.ts (2 errors) ==== + export declare let x: null | { foo: { bar: string | null } | undefined } | undefined; + export declare let y: { foo: { bar: number | undefined } }; + + x = y; + ~ +!!! error TS2322: Type '{ foo: { bar: number; }; }' is not assignable to type '{ foo: { bar: string; }; }'. +!!! error TS2322: Types of property 'foo' are incompatible. +!!! error TS2322: Type '{ bar: number; }' is not assignable to type '{ bar: string; }'. +!!! error TS2322: Types of property 'bar' are incompatible. +!!! error TS2322: Type 'number' is not assignable to type 'string'. + + y = x; + ~ +!!! error TS2322: Type '{ foo: { bar: string; }; }' is not assignable to type '{ foo: { bar: number; }; }'. +!!! error TS2322: Types of property 'foo' are incompatible. +!!! error TS2322: Type '{ bar: string; }' is not assignable to type '{ bar: number; }'. +!!! error TS2322: Types of property 'bar' are incompatible. +!!! error TS2322: Type 'string' is not assignable to type 'number'. \ No newline at end of file diff --git a/tests/baselines/reference/elaboratedErrorsOnNullableTargets01.js b/tests/baselines/reference/elaboratedErrorsOnNullableTargets01.js new file mode 100644 index 0000000000000..5c5f870021870 --- /dev/null +++ b/tests/baselines/reference/elaboratedErrorsOnNullableTargets01.js @@ -0,0 +1,13 @@ +//// [elaboratedErrorsOnNullableTargets01.ts] +export declare let x: null | { foo: { bar: string | null } | undefined } | undefined; +export declare let y: { foo: { bar: number | undefined } }; + +x = y; + +y = x; + +//// [elaboratedErrorsOnNullableTargets01.js] +"use strict"; +exports.__esModule = true; +exports.x = exports.y; +exports.y = exports.x; diff --git a/tests/baselines/reference/elaboratedErrorsOnNullableTargets01.symbols b/tests/baselines/reference/elaboratedErrorsOnNullableTargets01.symbols new file mode 100644 index 0000000000000..4f218b2e28f8e --- /dev/null +++ b/tests/baselines/reference/elaboratedErrorsOnNullableTargets01.symbols @@ -0,0 +1,19 @@ +=== tests/cases/compiler/elaboratedErrorsOnNullableTargets01.ts === +export declare let x: null | { foo: { bar: string | null } | undefined } | undefined; +>x : Symbol(x, Decl(elaboratedErrorsOnNullableTargets01.ts, 0, 18)) +>foo : Symbol(foo, Decl(elaboratedErrorsOnNullableTargets01.ts, 0, 30)) +>bar : Symbol(bar, Decl(elaboratedErrorsOnNullableTargets01.ts, 0, 37)) + +export declare let y: { foo: { bar: number | undefined } }; +>y : Symbol(y, Decl(elaboratedErrorsOnNullableTargets01.ts, 1, 18)) +>foo : Symbol(foo, Decl(elaboratedErrorsOnNullableTargets01.ts, 1, 23)) +>bar : Symbol(bar, Decl(elaboratedErrorsOnNullableTargets01.ts, 1, 30)) + +x = y; +>x : Symbol(x, Decl(elaboratedErrorsOnNullableTargets01.ts, 0, 18)) +>y : Symbol(y, Decl(elaboratedErrorsOnNullableTargets01.ts, 1, 18)) + +y = x; +>y : Symbol(y, Decl(elaboratedErrorsOnNullableTargets01.ts, 1, 18)) +>x : Symbol(x, Decl(elaboratedErrorsOnNullableTargets01.ts, 0, 18)) + diff --git a/tests/baselines/reference/elaboratedErrorsOnNullableTargets01.types b/tests/baselines/reference/elaboratedErrorsOnNullableTargets01.types new file mode 100644 index 0000000000000..e580b90a732ab --- /dev/null +++ b/tests/baselines/reference/elaboratedErrorsOnNullableTargets01.types @@ -0,0 +1,23 @@ +=== tests/cases/compiler/elaboratedErrorsOnNullableTargets01.ts === +export declare let x: null | { foo: { bar: string | null } | undefined } | undefined; +>x : { foo: { bar: string; }; } +>null : null +>foo : { bar: string; } +>bar : string +>null : null + +export declare let y: { foo: { bar: number | undefined } }; +>y : { foo: { bar: number; }; } +>foo : { bar: number; } +>bar : number + +x = y; +>x = y : { foo: { bar: number; }; } +>x : { foo: { bar: string; }; } +>y : { foo: { bar: number; }; } + +y = x; +>y = x : { foo: { bar: string; }; } +>y : { foo: { bar: number; }; } +>x : { foo: { bar: string; }; } + diff --git a/tests/baselines/reference/nestedFreshLiteral.errors.txt b/tests/baselines/reference/nestedFreshLiteral.errors.txt index d68b398449d53..5eea71dba81a9 100644 --- a/tests/baselines/reference/nestedFreshLiteral.errors.txt +++ b/tests/baselines/reference/nestedFreshLiteral.errors.txt @@ -1,10 +1,9 @@ tests/cases/compiler/nestedFreshLiteral.ts(12,21): error TS2322: Type '{ nested: { prop: { colour: string; }; }; }' is not assignable to type 'NestedCSSProps'. Types of property 'nested' are incompatible. - Type '{ prop: { colour: string; }; }' is not assignable to type 'NestedSelector | undefined'. - Type '{ prop: { colour: string; }; }' is not assignable to type 'NestedSelector'. - Types of property 'prop' are incompatible. - Type '{ colour: string; }' is not assignable to type 'CSSProps'. - Object literal may only specify known properties, but 'colour' does not exist in type 'CSSProps'. Did you mean to write 'color'? + Type '{ prop: { colour: string; }; }' is not assignable to type 'NestedSelector'. + Types of property 'prop' are incompatible. + Type '{ colour: string; }' is not assignable to type 'CSSProps'. + Object literal may only specify known properties, but 'colour' does not exist in type 'CSSProps'. Did you mean to write 'color'? ==== tests/cases/compiler/nestedFreshLiteral.ts (1 errors) ==== @@ -23,9 +22,8 @@ tests/cases/compiler/nestedFreshLiteral.ts(12,21): error TS2322: Type '{ nested: ~~~~~~~~~~~~~ !!! error TS2322: Type '{ nested: { prop: { colour: string; }; }; }' is not assignable to type 'NestedCSSProps'. !!! error TS2322: Types of property 'nested' are incompatible. -!!! error TS2322: Type '{ prop: { colour: string; }; }' is not assignable to type 'NestedSelector | undefined'. -!!! error TS2322: Type '{ prop: { colour: string; }; }' is not assignable to type 'NestedSelector'. -!!! error TS2322: Types of property 'prop' are incompatible. -!!! error TS2322: Type '{ colour: string; }' is not assignable to type 'CSSProps'. -!!! error TS2322: Object literal may only specify known properties, but 'colour' does not exist in type 'CSSProps'. Did you mean to write 'color'? +!!! error TS2322: Type '{ prop: { colour: string; }; }' is not assignable to type 'NestedSelector'. +!!! error TS2322: Types of property 'prop' are incompatible. +!!! error TS2322: Type '{ colour: string; }' is not assignable to type 'CSSProps'. +!!! error TS2322: Object literal may only specify known properties, but 'colour' does not exist in type 'CSSProps'. Did you mean to write 'color'? } \ No newline at end of file diff --git a/tests/cases/compiler/elaboratedErrorsOnNullableTargets01.ts b/tests/cases/compiler/elaboratedErrorsOnNullableTargets01.ts new file mode 100644 index 0000000000000..22f71c88d5ddb --- /dev/null +++ b/tests/cases/compiler/elaboratedErrorsOnNullableTargets01.ts @@ -0,0 +1,7 @@ + +export declare let x: null | { foo: { bar: string | null } | undefined } | undefined; +export declare let y: { foo: { bar: number | undefined } }; + +x = y; + +y = x; \ No newline at end of file