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

TS demands that abstract properties with type never are implemented #55121

Closed
RobertSandiford opened this issue Jul 23, 2023 · 17 comments
Closed
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@RobertSandiford
Copy link

Bug Report

🔎 Search Terms

abstract property inheritance never

🕗 Version & Regression Information

Current nightly v5.2.0-dev.20230720

⏯ Playground Link

Playground

💻 Code

abstract class P {
  abstract foo: never
}

class C extends P {} // error C does not implement foo

class D extends P {
  foo: never // Property 'foo' has no initializer and is not definitely assigned in the constructor.
}

🙁 Actual behavior

Error in class C, TS demands foo to be implemented.

🙂 Expected behavior

class C should not produce an error. Absence of foo matches the type constraint in P.

Comments

I suppose this is a bug - certainly I did not expect TS to ask me to implement a property with type never. A function with a type never argument would not let me pass it, and I expected abstract properties to be similar.

function f(a: never) {}
f(true) // error

"Fixing" this provides the benefit of being able to create optional abstract properties, e.g.

abstract class P {
  abstract foo: boolean | never
}

class C extends P {} // TS should allow this child class, because of the never option.

Relates to #40635 (Optional abstract properties)

@Jamesernator
Copy link

Jamesernator commented Jul 24, 2023

I would say that TypeScript's behaviour here makes sense, the type definition says it needs a field of foo: never and subclass doesn't have one.

"Fixing" this provides the benefit of being able to create optional abstract properties, e.g.

If this is the goal, it would make more sense for it to use optional field syntax:

abstract class P {
  abstract foo?: number;
}

class C extends P {} // error C does not implement foo

though unfortunately this doesn't work either.

@MartinJohns
Copy link
Contributor

A property typed never does not mean that property never exists. It's a property that will throw an error upon accessing.

@RobertSandiford
Copy link
Author

I would say that TypeScript's behaviour here makes sense, the type definition says it needs a field of foo: never and subclass doesn't have one.

"Fixing" this provides the benefit of being able to create optional abstract properties, e.g.

If this is the goal, it would make more sense for it to use optional field syntax:

abstract class P {
  abstract foo?: number;
}

class C extends P {} // error C does not implement foo

though unfortunately this doesn't work either.

I understand never as meaning "this never happens". A function that always throws never returns. A function argument that is never cannot be passed. An abstract property of type never is never declared on a child class.

I didn't explain well, but never is different to ?: - ?: Can be satisfied by prop = undefined where as never cannot. JS has two types of undefined on objects, prop not set, and prop set to value of undefined. Never would allow limiting to the first option only.

I support respecting ?: syntax in addition

@RobertSandiford
Copy link
Author

A property typed never does not mean that property never exists. It's a property that will throw an error upon accessing.

TS may behave that way with object typings, because JS objects are extensible, and object types are essentially interfaces (in the way that interfaces exist in other languages, stating possibly only part of the object content).

But generally never is applied strictly - it would be more useful to apply it strictly in classes derived from abstract classes. I don't think we are in an "interface" paradigm at that point. But I'm not totally sure - I'm sure we can extend classes with funky JS wonk, but is that allowed/recommended in TS?

@MartinJohns
Copy link
Contributor

#47071 (comment)

Record<string, never> describes a type that, if you accessed any of its properties, you'd get an exception.

Ergo properties that are typed never explicit mean "will throw upon access", not "does never exist".

TypeScript does not have a way to describe a type that never has a specific property. For this you would require the "Exact Types" feature: #12936

@RobertSandiford
Copy link
Author

#47071 (comment)

Record<string, never> describes a type that, if you accessed any of its properties, you'd get an exception.

Ergo properties that are typed never explicit mean "will throw upon access", not "does never exist".

TypeScript does not have a way to describe a type that never has a specific property. For this you would require the "Exact Types" feature: #12936

Right, so it's a language issue

@RyanCavanaugh RyanCavanaugh added the Working as Intended The behavior described is the intended behavior; this is not a bug label Jul 24, 2023
@RyanCavanaugh
Copy link
Member

RyanCavanaugh commented Jul 24, 2023

This behavior follows from the plain definitions of abstract and never. The fact that you can't combine these things to solve your original problem just means you've tried to do the wrong thing. I don't see what the bug is here.

boolean | never is just another way to say boolean so I think there is some confusion about what never means?

@RobertSandiford
Copy link
Author

This behavior follows from the plain definitions of abstract and never. The fact that you can't combine these things to solve your original problem just means you've tried to do the wrong thing. I don't see what the bug is here.

boolean | never is just another way to say boolean so I think there is some confusion about what never means?

Yes, I think now that never has 2 meanings.

  1. Accessing/calling thing thing never completes (exception, program exit, etc)
  2. This type cannot be satisfied / nothing matches this type (e.g. true & false == never)

But it does not mean absence of a thing.

@RobertSandiford
Copy link
Author

#55143

@fatcerberus
Copy link

The first point is a direct consequence of the second: the type can’t be satisfied, it’s an empty set, so any operation that “produces” a value of type never cannot ever complete normally according to the type system and must throw an exception. To do otherwise would be a type violation.

To be clear: the only reason absent properties can’t be typed never is because they evaluate to undefined (a unit type) at runtime. If it was a runtime error to access them instead, then this typing would make sense.

@RobertSandiford
Copy link
Author

The first point is a direct consequence of the second: the type can’t be satisfied, it’s an empty set, so any operation that “produces” a value of type never cannot ever complete normally according to the type system and must throw an exception. To do otherwise would be a type violation.

Thanks for the explanation. Do you disagree with me when I say that a routine that returns never could be a routine that exits the application, or an infinite loop, instead of an exception? But you and Ryan have said that it must throw an exception, yet the other 2 examples are both accepted by the type checker.

@RyanCavanaugh
Copy link
Member

Which other 2 examples?

@fatcerberus
Copy link

Do you disagree with me when I say that a routine that returns never could be a routine that exits the application, or an infinite loop, instead of an exception?

No, I don’t disagree. I just consider those cases analogous (i.e. normal control flow diverges) so I say “throw an exception” as shorthand, as that is by far the most common case. The main point is that the code that “asks” for a value of type never… never runs.

@RobertSandiford
Copy link
Author

Which other 2 examples?

A func that exits the process (process.exit in node) or a func with an infinite loop

@RobertSandiford
Copy link
Author

RobertSandiford commented Jul 25, 2023

Do you disagree with me when I say that a routine that returns never could be a routine that exits the application, or an infinite loop, instead of an exception?

No, I don’t disagree. I just consider those cases analogous (i.e. normal control flow diverges) so I say “throw an exception” as shorthand, as that is by far the most common case. The main point is that the code that “asks” for a value of type never… never runs.

Yes I think we agree. Please don't use that shorthand though :) it's confusing because it suggests that somehow never is related to exceptions, which it's really not besides casuality

@fatcerberus
Copy link

fatcerberus commented Jul 26, 2023

fwiw, I sometimes feel like never was misnamed, as people do often interpret it to mean “this thing never occurs”, when what it actually means is that you can never observe its value. It makes a lot more sense when you think of it in terms of set theory - you can’t pick an element from an empty set, so if you must do so, you have no choice except to refuse to return to the caller.

But alas, that ship sailed long ago. 😄

@RyanCavanaugh
Copy link
Member

Blame Scala 😉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

5 participants