diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b67248fcb294d..e3a97b9158217 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -110,7 +110,7 @@ namespace ts { const unknownSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, "unknown"); const resolvingSymbol = createSymbol(SymbolFlags.Transient, "__resolving__"); - const nullableWideningFlags = strictNullChecks ? 0 : TypeFlags.ContainsUndefinedOrNull; + const nullableWideningFlags = strictNullChecks ? 0 : TypeFlags.ContainsWideningType; const anyType = createIntrinsicType(TypeFlags.Any, "any"); const stringType = createIntrinsicType(TypeFlags.String, "string"); const numberType = createIntrinsicType(TypeFlags.Number, "number"); @@ -119,9 +119,9 @@ namespace ts { const voidType = createIntrinsicType(TypeFlags.Void, "void"); const undefinedType = createIntrinsicType(TypeFlags.Undefined | nullableWideningFlags, "undefined"); const nullType = createIntrinsicType(TypeFlags.Null | nullableWideningFlags, "null"); - const emptyArrayElementType = createIntrinsicType(TypeFlags.Undefined | TypeFlags.ContainsUndefinedOrNull, "undefined"); const unknownType = createIntrinsicType(TypeFlags.Any, "unknown"); const neverType = createIntrinsicType(TypeFlags.Never, "never"); + const wideningNeverType = createIntrinsicType(TypeFlags.Never | TypeFlags.ContainsWideningType, "never"); const emptyObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); const emptyGenericType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); @@ -5008,7 +5008,7 @@ namespace ts { if (type.flags & TypeFlags.Undefined) typeSet.containsUndefined = true; if (type.flags & TypeFlags.Null) typeSet.containsNull = true; } - else if (type !== neverType && !contains(typeSet, type)) { + else if (!(type.flags & TypeFlags.Never) && !contains(typeSet, type)) { typeSet.push(type); } } @@ -5867,7 +5867,7 @@ namespace ts { if (!(target.flags & TypeFlags.Never)) { if (target.flags & TypeFlags.Any || source.flags & TypeFlags.Never) return Ternary.True; if (source.flags & TypeFlags.Undefined) { - if (!strictNullChecks || target.flags & (TypeFlags.Undefined | TypeFlags.Void) || source === emptyArrayElementType) return Ternary.True; + if (!strictNullChecks || target.flags & (TypeFlags.Undefined | TypeFlags.Void)) return Ternary.True; } if (source.flags & TypeFlags.Null) { if (!strictNullChecks || target.flags & TypeFlags.Null) return Ternary.True; @@ -6781,7 +6781,7 @@ namespace ts { // A type is array-like if it is a reference to the global Array or global ReadonlyArray type, // or if it is not the undefined or null type and if it is assignable to ReadonlyArray return type.flags & TypeFlags.Reference && ((type).target === globalArrayType || (type).target === globalReadonlyArrayType) || - !(type.flags & TypeFlags.Nullable) && isTypeAssignableTo(type, anyReadonlyArrayType); + !(type.flags & TypeFlags.WideningType) && isTypeAssignableTo(type, anyReadonlyArrayType); } function isTupleLikeType(type: Type): boolean { @@ -6905,7 +6905,7 @@ namespace ts { function getWidenedType(type: Type): Type { if (type.flags & TypeFlags.RequiresWidening) { - if (type.flags & TypeFlags.Nullable) { + if (type.flags & TypeFlags.WideningType) { return anyType; } if (type.flags & TypeFlags.ObjectLiteral) { @@ -6957,7 +6957,7 @@ namespace ts { if (type.flags & TypeFlags.ObjectLiteral) { for (const p of getPropertiesOfObjectType(type)) { const t = getTypeOfSymbol(p); - if (t.flags & TypeFlags.ContainsUndefinedOrNull) { + if (t.flags & TypeFlags.ContainsWideningType) { if (!reportWideningErrorsInType(t)) { error(p.valueDeclaration, Diagnostics.Object_literal_s_property_0_implicitly_has_an_1_type, p.name, typeToString(getWidenedType(t))); } @@ -7004,7 +7004,7 @@ namespace ts { } function reportErrorsFromWidening(declaration: Declaration, type: Type) { - if (produceDiagnostics && compilerOptions.noImplicitAny && type.flags & TypeFlags.ContainsUndefinedOrNull) { + if (produceDiagnostics && compilerOptions.noImplicitAny && type.flags & TypeFlags.ContainsWideningType) { // Report implicit any error within type if possible, otherwise report error on declaration if (!reportWideningErrorsInType(type)) { reportImplicitAnyError(declaration, type); @@ -9187,7 +9187,8 @@ namespace ts { } } } - return createArrayType(elementTypes.length ? getUnionType(elementTypes) : emptyArrayElementType); + const elementType = getUnionType(elementTypes); + return createArrayType(elementType === neverType ? strictNullChecks ? wideningNeverType : undefinedType : elementType); } function isNumericName(name: DeclarationName): boolean { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 4e55956fa7c20..cff531e3d98c6 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2170,7 +2170,7 @@ namespace ts { /* @internal */ FreshObjectLiteral = 0x00100000, // Fresh object literal type /* @internal */ - ContainsUndefinedOrNull = 0x00200000, // Type is or contains undefined or null type + ContainsWideningType = 0x00200000, // Type is or contains primitive that widens to any /* @internal */ ContainsObjectLiteral = 0x00400000, // Type is or contains object literal type /* @internal */ @@ -2182,6 +2182,7 @@ namespace ts { /* @internal */ Nullable = Undefined | Null, + WideningType = Undefined | Null | Never, /* @internal */ Intrinsic = Any | String | Number | Boolean | ESSymbol | Void | Undefined | Null | Never, /* @internal */ @@ -2193,9 +2194,9 @@ namespace ts { StructuredType = ObjectType | Union | Intersection, Narrowable = Any | ObjectType | Union | TypeParameter, /* @internal */ - RequiresWidening = ContainsUndefinedOrNull | ContainsObjectLiteral, + RequiresWidening = ContainsWideningType | ContainsObjectLiteral, /* @internal */ - PropagatingFlags = ContainsUndefinedOrNull | ContainsObjectLiteral | ContainsAnyFunctionType + PropagatingFlags = ContainsWideningType | ContainsObjectLiteral | ContainsAnyFunctionType } export type DestructuringPattern = BindingPattern | ObjectLiteralExpression | ArrayLiteralExpression; diff --git a/tests/baselines/reference/neverTypeWidening.js b/tests/baselines/reference/neverTypeWidening.js new file mode 100644 index 0000000000000..89ff4edf7995a --- /dev/null +++ b/tests/baselines/reference/neverTypeWidening.js @@ -0,0 +1,26 @@ +//// [neverTypeWidening.ts] + +let a = []; // never[] widens to any[] +let b = [...[], ...[]]; // never[] widens to any[] +let c = [...[...[]]]; // never[] widens to any[] +let d = [...[], ...[1, 2, 3]]; // number[] +let e = [1, 2, 3].concat([]); // number[] + +// Repro from #8878 + +function concat(xs: T[], ys: T[]): T[] { + return [...xs, ...ys]; +} +const y = concat([], ["a"]); + +//// [neverTypeWidening.js] +var a = []; // never[] widens to any[] +var b = [].concat([]); // never[] widens to any[] +var c = []; // never[] widens to any[] +var d = [].concat([1, 2, 3]); // number[] +var e = [1, 2, 3].concat([]); // number[] +// Repro from #8878 +function concat(xs, ys) { + return xs.concat(ys); +} +var y = concat([], ["a"]); diff --git a/tests/baselines/reference/neverTypeWidening.symbols b/tests/baselines/reference/neverTypeWidening.symbols new file mode 100644 index 0000000000000..ab1e96338c602 --- /dev/null +++ b/tests/baselines/reference/neverTypeWidening.symbols @@ -0,0 +1,38 @@ +=== tests/cases/compiler/neverTypeWidening.ts === + +let a = []; // never[] widens to any[] +>a : Symbol(a, Decl(neverTypeWidening.ts, 1, 3)) + +let b = [...[], ...[]]; // never[] widens to any[] +>b : Symbol(b, Decl(neverTypeWidening.ts, 2, 3)) + +let c = [...[...[]]]; // never[] widens to any[] +>c : Symbol(c, Decl(neverTypeWidening.ts, 3, 3)) + +let d = [...[], ...[1, 2, 3]]; // number[] +>d : Symbol(d, Decl(neverTypeWidening.ts, 4, 3)) + +let e = [1, 2, 3].concat([]); // number[] +>e : Symbol(e, Decl(neverTypeWidening.ts, 5, 3)) +>[1, 2, 3].concat : Symbol(Array.concat, Decl(lib.d.ts, --, --)) +>concat : Symbol(Array.concat, Decl(lib.d.ts, --, --)) + +// Repro from #8878 + +function concat(xs: T[], ys: T[]): T[] { +>concat : Symbol(concat, Decl(neverTypeWidening.ts, 5, 29)) +>T : Symbol(T, Decl(neverTypeWidening.ts, 9, 16)) +>xs : Symbol(xs, Decl(neverTypeWidening.ts, 9, 19)) +>T : Symbol(T, Decl(neverTypeWidening.ts, 9, 16)) +>ys : Symbol(ys, Decl(neverTypeWidening.ts, 9, 27)) +>T : Symbol(T, Decl(neverTypeWidening.ts, 9, 16)) +>T : Symbol(T, Decl(neverTypeWidening.ts, 9, 16)) + + return [...xs, ...ys]; +>xs : Symbol(xs, Decl(neverTypeWidening.ts, 9, 19)) +>ys : Symbol(ys, Decl(neverTypeWidening.ts, 9, 27)) +} +const y = concat([], ["a"]); +>y : Symbol(y, Decl(neverTypeWidening.ts, 12, 5)) +>concat : Symbol(concat, Decl(neverTypeWidening.ts, 5, 29)) + diff --git a/tests/baselines/reference/neverTypeWidening.types b/tests/baselines/reference/neverTypeWidening.types new file mode 100644 index 0000000000000..98d3dc99e6204 --- /dev/null +++ b/tests/baselines/reference/neverTypeWidening.types @@ -0,0 +1,70 @@ +=== tests/cases/compiler/neverTypeWidening.ts === + +let a = []; // never[] widens to any[] +>a : any[] +>[] : never[] + +let b = [...[], ...[]]; // never[] widens to any[] +>b : any[] +>[...[], ...[]] : never[] +>...[] : never +>[] : never[] +>...[] : never +>[] : never[] + +let c = [...[...[]]]; // never[] widens to any[] +>c : any[] +>[...[...[]]] : never[] +>...[...[]] : never +>[...[]] : never[] +>...[] : never +>[] : never[] + +let d = [...[], ...[1, 2, 3]]; // number[] +>d : number[] +>[...[], ...[1, 2, 3]] : number[] +>...[] : never +>[] : never[] +>...[1, 2, 3] : number +>[1, 2, 3] : number[] +>1 : number +>2 : number +>3 : number + +let e = [1, 2, 3].concat([]); // number[] +>e : number[] +>[1, 2, 3].concat([]) : number[] +>[1, 2, 3].concat : (...items: (number | number[])[]) => number[] +>[1, 2, 3] : number[] +>1 : number +>2 : number +>3 : number +>concat : (...items: (number | number[])[]) => number[] +>[] : never[] + +// Repro from #8878 + +function concat(xs: T[], ys: T[]): T[] { +>concat : (xs: T[], ys: T[]) => T[] +>T : T +>xs : T[] +>T : T +>ys : T[] +>T : T +>T : T + + return [...xs, ...ys]; +>[...xs, ...ys] : T[] +>...xs : T +>xs : T[] +>...ys : T +>ys : T[] +} +const y = concat([], ["a"]); +>y : string[] +>concat([], ["a"]) : string[] +>concat : (xs: T[], ys: T[]) => T[] +>[] : never[] +>["a"] : string[] +>"a" : string + diff --git a/tests/cases/compiler/neverTypeWidening.ts b/tests/cases/compiler/neverTypeWidening.ts new file mode 100644 index 0000000000000..d53d700847caf --- /dev/null +++ b/tests/cases/compiler/neverTypeWidening.ts @@ -0,0 +1,14 @@ +// @strictNullChecks: true + +let a = []; // never[] widens to any[] +let b = [...[], ...[]]; // never[] widens to any[] +let c = [...[...[]]]; // never[] widens to any[] +let d = [...[], ...[1, 2, 3]]; // number[] +let e = [1, 2, 3].concat([]); // number[] + +// Repro from #8878 + +function concat(xs: T[], ys: T[]): T[] { + return [...xs, ...ys]; +} +const y = concat([], ["a"]); \ No newline at end of file