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

Use 'never' type for empty array literals #8907

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
19 changes: 10 additions & 9 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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");
Copy link
Member

@DanielRosenwasser DanielRosenwasser Jun 1, 2016

Choose a reason for hiding this comment

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

Leave a comment on what a widening never implies over an unwidened never and why you need it.


const emptyObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
const emptyGenericType = <GenericType><ObjectType>createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
Expand Down Expand Up @@ -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);
}
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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<any>
return type.flags & TypeFlags.Reference && ((<TypeReference>type).target === globalArrayType || (<TypeReference>type).target === globalReadonlyArrayType) ||
!(type.flags & TypeFlags.Nullable) && isTypeAssignableTo(type, anyReadonlyArrayType);
!(type.flags & TypeFlags.WideningType) && isTypeAssignableTo(type, anyReadonlyArrayType);
}

function isTupleLikeType(type: Type): boolean {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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)));
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Copy link
Member

Choose a reason for hiding this comment

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

So if you have an array of [a, b, c], and a, b, and c each have type never, does the type of the array literal get widened to any[]?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, the array literal would widen to any[]. We could do extra work to aggregate widening and only widen only if all (or if just one?) of the elements are of the widening never type. But it hardly seems worth it.

}

function isNumericName(name: DeclarationName): boolean {
Expand Down
7 changes: 4 additions & 3 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand All @@ -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 */
Expand All @@ -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;
Expand Down
26 changes: 26 additions & 0 deletions tests/baselines/reference/neverTypeWidening.js
Original file line number Diff line number Diff line change
@@ -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<T>(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"]);
38 changes: 38 additions & 0 deletions tests/baselines/reference/neverTypeWidening.symbols
Original file line number Diff line number Diff line change
@@ -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<T>(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))

70 changes: 70 additions & 0 deletions tests/baselines/reference/neverTypeWidening.types
Original file line number Diff line number Diff line change
@@ -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<T>(xs: T[], ys: T[]): T[] {
>concat : <T>(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 : <T>(xs: T[], ys: T[]) => T[]
>[] : never[]
>["a"] : string[]
>"a" : string

14 changes: 14 additions & 0 deletions tests/cases/compiler/neverTypeWidening.ts
Original file line number Diff line number Diff line change
@@ -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<T>(xs: T[], ys: T[]): T[] {
return [...xs, ...ys];
}
const y = concat([], ["a"]);