Skip to content

Disallow more flavors of property accesses on never in expression space #56780

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

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
6 changes: 3 additions & 3 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10863,7 +10863,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
mapType(baseConstraint, t => sliceTupleType(t as TupleTypeReference, index)) :
createArrayType(elementType);
}
else if (isArrayLikeType(parentType)) {
else if (isArrayLikeType(parentType) && !isErrorType(elementType)) {
const indexType = getNumberLiteralType(index);
const accessFlags = AccessFlags.ExpressionPosition | (noTupleBoundsCheck || hasDefaultValue(declaration) ? AccessFlags.NoTupleBoundsCheck : 0);
const declaredType = getIndexedAccessTypeOrUndefined(parentType, indexType, accessFlags, declaration.name) || errorType;
Expand Down Expand Up @@ -18040,7 +18040,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
}
if (!(indexType.flags & TypeFlags.Nullable) && isTypeAssignableToKind(indexType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbolLike)) {
if (objectType.flags & (TypeFlags.Any | TypeFlags.Never)) {
if (objectType.flags & TypeFlags.Any || objectType.flags & TypeFlags.Never && !(accessFlags & AccessFlags.ExpressionPosition)) {
return objectType;
}
// If no index signature is applicable, we default to the string index signature. In effect, this means the string
Expand Down Expand Up @@ -42893,7 +42893,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const allowAsyncIterables = (use & IterationUse.AllowsAsyncIterablesFlag) !== 0;
if (inputType === neverType) {
reportTypeNotIterableError(errorNode!, inputType, allowAsyncIterables); // TODO: GH#18217
return undefined;
return errorType;
}

const uplevelIteration = languageVersion >= ScriptTarget.ES2015;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ function foo(x: X): 1 {

default:
const [n] = a;
>n : never
>n : any
>a : never

return a;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
controlFlowAssignmentPatternOrder.ts(6,9): error TS2339: Property '1' does not exist on type 'never'.
controlFlowAssignmentPatternOrder.ts(12,9): error TS2339: Property '0' does not exist on type 'never'.
controlFlowAssignmentPatternOrder.ts(31,9): error TS2339: Property '1' does not exist on type 'never'.
controlFlowAssignmentPatternOrder.ts(37,9): error TS2339: Property '0' does not exist on type 'never'.
controlFlowAssignmentPatternOrder.ts(56,14): error TS2339: Property '1' does not exist on type 'never'.
controlFlowAssignmentPatternOrder.ts(62,14): error TS2339: Property '0' does not exist on type 'never'.


==== controlFlowAssignmentPatternOrder.ts (6 errors) ====
// https://github.com/microsoft/TypeScript/pull/41094#issuecomment-716044363
declare function f(): void;
{
let a: 0 | 1 = 0;
let b: 0 | 1 | 9;
[{ [(a = 1)]: b } = [9, a] as const] = [];
~~~~~~~
!!! error TS2339: Property '1' does not exist on type 'never'.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not entirely sure if the error here is completely desirable but it feels consistent to me with things like:

const obj = { b: '' }
let a: number
;({ a = 10 } = obj) // Property 'a' does not exist on type '{ b: string; }'.(2339)

What I mean is that despite the first element in this array pattern having an initializer it's the "right-side" type of the original source that is used to check for valid property accesses.

So since we have never[] on the right side, its first element is actually never and that has no properties so disallowing those patterns here looks desirable to me.

Copy link
Member

Choose a reason for hiding this comment

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

This error doesn't look right to me: it says "Property '1' does not exist on type 'never'", but we should be getting property 1 of type [9, typeof a], and that tuple should not have type never.

const bb: 0 = b;
}
{
let a: 0 | 1 = 1;
let b: 0 | 1 | 9;
[{ [a]: b } = [9, a = 0] as const] = [];
~
!!! error TS2339: Property '0' does not exist on type 'never'.
const bb: 9 = b;
}
{
let a: 0 | 1 = 0;
let b: 0 | 1 | 8 | 9;
[{ [(a = 1)]: b } = [9, a] as const] = [[9, 8] as const];
const bb: 0 | 8 = b;
}
{
let a: 0 | 1 = 1;
let b: 0 | 1 | 8 | 9;
[{ [a]: b } = [a = 0, 9] as const] = [[8, 9] as const];
const bb: 0 | 8 = b;
}
// same as above but on left of a binary expression
{
let a: 0 | 1 = 0;
let b: 0 | 1 | 9;
[{ [(a = 1)]: b } = [9, a] as const] = [], f();
~~~~~~~
!!! error TS2339: Property '1' does not exist on type 'never'.
const bb: 0 = b;
}
{
let a: 0 | 1 = 1;
let b: 0 | 1 | 9;
[{ [a]: b } = [9, a = 0] as const] = [], f();
~
!!! error TS2339: Property '0' does not exist on type 'never'.
const bb: 9 = b;
}
{
let a: 0 | 1 = 0;
let b: 0 | 1 | 8 | 9;
[{ [(a = 1)]: b } = [9, a] as const] = [[9, 8] as const], f();
const bb: 0 | 8 = b;
}
{
let a: 0 | 1 = 1;
let b: 0 | 1 | 8 | 9;
[{ [a]: b } = [a = 0, 9] as const] = [[8, 9] as const], f();
const bb: 0 | 8 = b;
}
// same as above but on right of a binary expression
{
let a: 0 | 1 = 0;
let b: 0 | 1 | 9;
f(), [{ [(a = 1)]: b } = [9, a] as const] = [];
~~~~~~~
!!! error TS2339: Property '1' does not exist on type 'never'.
const bb: 0 = b;
}
{
let a: 0 | 1 = 1;
let b: 0 | 1 | 9;
f(), [{ [a]: b } = [9, a = 0] as const] = [];
~
!!! error TS2339: Property '0' does not exist on type 'never'.
const bb: 9 = b;
}
{
let a: 0 | 1 = 0;
let b: 0 | 1 | 8 | 9;
f(), [{ [(a = 1)]: b } = [9, a] as const] = [[9, 8] as const];
const bb: 0 | 8 = b;
}
{
let a: 0 | 1 = 1;
let b: 0 | 1 | 8 | 9;
f(), [{ [a]: b } = [a = 0, 9] as const] = [[8, 9] as const];
const bb: 0 | 8 = b;
}
20 changes: 20 additions & 0 deletions tests/baselines/reference/objectDestructuringInSwitch1.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
objectDestructuringInSwitch1.ts(11,15): error TS2339: Property 'prop' does not exist on type 'never'.


==== objectDestructuringInSwitch1.ts (1 errors) ====
type X = { kind: "a", a: { prop: 1 } } | { kind: "b", a: {} }

function foo(x: X): 1 {
const { kind, a } = x;
switch (kind) {
case "a":
return a.prop;
case "b":
return 1;
default:
const { prop } = a;
~~~~
!!! error TS2339: Property 'prop' does not exist on type 'never'.
return a;
}
}
41 changes: 41 additions & 0 deletions tests/baselines/reference/objectDestructuringInSwitch1.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//// [tests/cases/compiler/objectDestructuringInSwitch1.ts] ////

=== objectDestructuringInSwitch1.ts ===
type X = { kind: "a", a: { prop: 1 } } | { kind: "b", a: {} }
>X : Symbol(X, Decl(objectDestructuringInSwitch1.ts, 0, 0))
>kind : Symbol(kind, Decl(objectDestructuringInSwitch1.ts, 0, 10))
>a : Symbol(a, Decl(objectDestructuringInSwitch1.ts, 0, 21))
>prop : Symbol(prop, Decl(objectDestructuringInSwitch1.ts, 0, 26))
>kind : Symbol(kind, Decl(objectDestructuringInSwitch1.ts, 0, 42))
>a : Symbol(a, Decl(objectDestructuringInSwitch1.ts, 0, 53))

function foo(x: X): 1 {
>foo : Symbol(foo, Decl(objectDestructuringInSwitch1.ts, 0, 61))
>x : Symbol(x, Decl(objectDestructuringInSwitch1.ts, 2, 13))
>X : Symbol(X, Decl(objectDestructuringInSwitch1.ts, 0, 0))

const { kind, a } = x;
>kind : Symbol(kind, Decl(objectDestructuringInSwitch1.ts, 3, 9))
>a : Symbol(a, Decl(objectDestructuringInSwitch1.ts, 3, 15))
>x : Symbol(x, Decl(objectDestructuringInSwitch1.ts, 2, 13))

switch (kind) {
>kind : Symbol(kind, Decl(objectDestructuringInSwitch1.ts, 3, 9))

case "a":
return a.prop;
>a.prop : Symbol(prop, Decl(objectDestructuringInSwitch1.ts, 0, 26))
>a : Symbol(a, Decl(objectDestructuringInSwitch1.ts, 3, 15))
>prop : Symbol(prop, Decl(objectDestructuringInSwitch1.ts, 0, 26))

case "b":
return 1;
default:
const { prop } = a;
>prop : Symbol(prop, Decl(objectDestructuringInSwitch1.ts, 10, 13))
>a : Symbol(a, Decl(objectDestructuringInSwitch1.ts, 3, 15))

return a;
>a : Symbol(a, Decl(objectDestructuringInSwitch1.ts, 3, 15))
}
}
46 changes: 46 additions & 0 deletions tests/baselines/reference/objectDestructuringInSwitch1.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//// [tests/cases/compiler/objectDestructuringInSwitch1.ts] ////

=== objectDestructuringInSwitch1.ts ===
type X = { kind: "a", a: { prop: 1 } } | { kind: "b", a: {} }
>X : { kind: "a"; a: { prop: 1;}; } | { kind: "b"; a: {}; }
>kind : "a"
>a : { prop: 1; }
>prop : 1
>kind : "b"
>a : {}

function foo(x: X): 1 {
>foo : (x: X) => 1
>x : X

const { kind, a } = x;
>kind : "a" | "b"
>a : {} | { prop: 1; }
>x : X

switch (kind) {
>kind : "a" | "b"

case "a":
>"a" : "a"

return a.prop;
>a.prop : 1
>a : { prop: 1; }
>prop : 1

case "b":
>"b" : "b"

return 1;
>1 : 1

default:
const { prop } = a;
>prop : any
>a : never

return a;
>a : never
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
parameterBindingPatternWithNever1.ts(5,29): error TS2339: Property 'foo' does not exist on type 'never'.


==== parameterBindingPatternWithNever1.ts (1 errors) ====
function something(foo: string) {}

type ComplexTypeThatReturnsNever = never;

function somethingWrapper({ foo }: ComplexTypeThatReturnsNever) {
~~~
!!! error TS2339: Property 'foo' does not exist on type 'never'.
something(foo);
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//// [tests/cases/compiler/parameterBindingPatternWithNever1.ts] ////

=== parameterBindingPatternWithNever1.ts ===
function something(foo: string) {}
>something : Symbol(something, Decl(parameterBindingPatternWithNever1.ts, 0, 0))
>foo : Symbol(foo, Decl(parameterBindingPatternWithNever1.ts, 0, 19))

type ComplexTypeThatReturnsNever = never;
>ComplexTypeThatReturnsNever : Symbol(ComplexTypeThatReturnsNever, Decl(parameterBindingPatternWithNever1.ts, 0, 34))

function somethingWrapper({ foo }: ComplexTypeThatReturnsNever) {
>somethingWrapper : Symbol(somethingWrapper, Decl(parameterBindingPatternWithNever1.ts, 2, 41))
>foo : Symbol(foo, Decl(parameterBindingPatternWithNever1.ts, 4, 27))
>ComplexTypeThatReturnsNever : Symbol(ComplexTypeThatReturnsNever, Decl(parameterBindingPatternWithNever1.ts, 0, 34))

something(foo);
>something : Symbol(something, Decl(parameterBindingPatternWithNever1.ts, 0, 0))
>foo : Symbol(foo, Decl(parameterBindingPatternWithNever1.ts, 4, 27))
}

20 changes: 20 additions & 0 deletions tests/baselines/reference/parameterBindingPatternWithNever1.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//// [tests/cases/compiler/parameterBindingPatternWithNever1.ts] ////

=== parameterBindingPatternWithNever1.ts ===
function something(foo: string) {}
>something : (foo: string) => void
>foo : string

type ComplexTypeThatReturnsNever = never;
>ComplexTypeThatReturnsNever : never

function somethingWrapper({ foo }: ComplexTypeThatReturnsNever) {
>somethingWrapper : ({ foo }: ComplexTypeThatReturnsNever) => void
>foo : any

something(foo);
>something(foo) : void
>something : (foo: string) => void
>foo : any
}

22 changes: 22 additions & 0 deletions tests/baselines/reference/propertyAccessOnNever1.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
propertyAccessOnNever1.ts(5,9): error TS2339: Property 'foo' does not exist on type 'never'.
propertyAccessOnNever1.ts(6,1): error TS7053: Element implicitly has an 'any' type because expression of type '"foo"' can't be used to index type 'never'.
Property 'foo' does not exist on type 'never'.
propertyAccessOnNever1.ts(7,9): error TS2339: Property 'foo' does not exist on type 'never'.


==== propertyAccessOnNever1.ts (3 errors) ====
// https://github.com/microsoft/TypeScript/issues/56778

declare const example: never;

example.foo;
~~~
!!! error TS2339: Property 'foo' does not exist on type 'never'.
example['foo'];
~~~~~~~~~~~~~~
!!! error TS7053: Element implicitly has an 'any' type because expression of type '"foo"' can't be used to index type 'never'.
!!! error TS7053: Property 'foo' does not exist on type 'never'.
const { foo } = example
~~~
!!! error TS2339: Property 'foo' does not exist on type 'never'.

18 changes: 18 additions & 0 deletions tests/baselines/reference/propertyAccessOnNever1.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//// [tests/cases/compiler/propertyAccessOnNever1.ts] ////

=== propertyAccessOnNever1.ts ===
// https://github.com/microsoft/TypeScript/issues/56778

declare const example: never;
>example : Symbol(example, Decl(propertyAccessOnNever1.ts, 2, 13))

example.foo;
>example : Symbol(example, Decl(propertyAccessOnNever1.ts, 2, 13))

example['foo'];
>example : Symbol(example, Decl(propertyAccessOnNever1.ts, 2, 13))

const { foo } = example
>foo : Symbol(foo, Decl(propertyAccessOnNever1.ts, 6, 7))
>example : Symbol(example, Decl(propertyAccessOnNever1.ts, 2, 13))

22 changes: 22 additions & 0 deletions tests/baselines/reference/propertyAccessOnNever1.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//// [tests/cases/compiler/propertyAccessOnNever1.ts] ////

=== propertyAccessOnNever1.ts ===
// https://github.com/microsoft/TypeScript/issues/56778

declare const example: never;
>example : never

example.foo;
>example.foo : any
>example : never
>foo : any

example['foo'];
>example['foo'] : any
>example : never
>'foo' : "foo"

const { foo } = example
>foo : any
>example : never

17 changes: 17 additions & 0 deletions tests/cases/compiler/objectDestructuringInSwitch1.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// @strict: true
// @noEmit: true

type X = { kind: "a", a: { prop: 1 } } | { kind: "b", a: {} }

function foo(x: X): 1 {
const { kind, a } = x;
switch (kind) {
case "a":
return a.prop;
case "b":
return 1;
default:
const { prop } = a;
return a;
}
}
10 changes: 10 additions & 0 deletions tests/cases/compiler/parameterBindingPatternWithNever1.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// @strict: true
// @noEmit: true

function something(foo: string) {}

type ComplexTypeThatReturnsNever = never;

function somethingWrapper({ foo }: ComplexTypeThatReturnsNever) {
something(foo);
}
Loading