Skip to content

Union type of an object literal type and empty object (using never as its field) not reporting type errors in some cases #51211

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
Yihao-G opened this issue Oct 18, 2022 · 6 comments

Comments

@Yihao-G
Copy link

Yihao-G commented Oct 18, 2022

Bug Report

typescript-eslint recommends the use of Record<string, never> to represent empty objects, because {} means anything but null or undefined. However, when this empty object type is used with other object literal types in a union type, TypeScript doesn't error where expected.

🔎 Search Terms

  • Empty object
  • never type
  • Object literal type
  • Union type

🕗 Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about never, empty object and union.

⏯ Playground Link

Playground link with relevant code

💻 Code

type Foo = { a: { b: string } } | Record<string, never>

let value: Foo = {
    a: {
        b: 'blahblah'
    }
}

value.a.b // no error (as expected, because of type narrowing)

value = {}

value.a.b // Property 'b' does not exist on type 'never'.(2339) (as expected, because of type narrowing)

function fun1() {
    console.log(value.a.b) // no error (expecting error)
}

function bar(foo: Foo) {
    console.log(foo.a.b) // no error (expecting error)
}

🙁 Actual behavior

In the functions, accessing the union-typed variable doesn't report errors.

🙂 Expected behavior

In the functions, accessing the union-typed variable should be reporting errors.

@MartinJohns
Copy link
Contributor

Another duplicate of #9998.

@Yihao-G
Copy link
Author

Yihao-G commented Oct 18, 2022

Sorry I didn't see the template of https://github.com/microsoft/TypeScript/issues/new?assignees=&labels=Duplicate&template=types-not-correct-in-with-callback.md&title=. This seems to fall under the second category of "function calls do not reset narrowing"?

But I'm still confused. With the example below, where fun1 doesn't report an error but fun2 reports. I guess TS handles {} differently from other types ({ [key: string]: never })?

type FooWithNever = { a: { b: string } } | Record<string, never>
type FooWithEmptyObj = { a: { b: string } } | {}

function fun1(foo: FooWithNever) {
    foo.a.b // No error
}

function fun2(foo: FooWithEmptyObj) {
    foo.a.b // Property 'a' does not exist on type '{}'.(2339)
}

@MartinJohns
Copy link
Contributor

MartinJohns commented Oct 18, 2022

I guess TS handles {} differently from other types ({ [key: string]: never })?

The type { [key: string]: never } basically means: Whatever property you access, it will/should throw an error (see this comment). It does not mean properties must not exist. You can't represent such types in TypeScript. I guess TypeScript falls into unsound behavior here, accessing a it creates a union of all a properties from the types (string | never), and never is an empty union type, so it gets reduced to string.

The type {} just means: not null or undefined, otherwise fair game.

@fatcerberus
Copy link

Also worth noting is that, despite ESLint’s objections, {} isn’t unique in allowing primitives - that’s just a consequence of structural typing. Primitives are also assignable to { toString(): string }, for example.

@MartinJohns
Copy link
Contributor

I completely skipped reading the part about ESLint. Bad defaults, confusing developers since forever.

@Yihao-G
Copy link
Author

Yihao-G commented Oct 18, 2022

That explains. Thanks guys. Will close the ticket now

@Yihao-G Yihao-G closed this as not planned Won't fix, can't repro, duplicate, stale Oct 18, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants