Skip to content

Incorrect type-error with interface and throws #51102

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
thedrlambda opened this issue Oct 7, 2022 · 10 comments
Closed

Incorrect type-error with interface and throws #51102

thedrlambda opened this issue Oct 7, 2022 · 10 comments
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@thedrlambda
Copy link

Bug Report

πŸ”Ž Search Terms

interface, throw

πŸ•— Version & Regression Information

My version is 4.8.2

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about throw and type-error

⏯ Playground Link

Playground link with relevant code

πŸ’» Code

interface SomeInterface {
  foo(): string | undefined;
}
class SomeInterfaceDummy implements SomeInterface {
  foo() /* : never */ {
    throw "Should not be called";
    // return "";
  }
}
class SomeInterfaceStub extends SomeInterfaceDummy {
  foo() {
    return "stub";
  }
}

πŸ™ Actual behavior

There is no way to type-check the code above. The return type is inferred as void (I expected never). Adding the out-commented return gives the correct return type but errors with "unreachable code." Putting the return type : never explicitly means the inheriting method doesn't compile.

πŸ™‚ Expected behavior

Body that throws is inferred to have type never and never <: any so there should be no errors.

@thedrlambda
Copy link
Author

For now, I am using this ugly workaround:

  foo(): ReturnType<SomeInterface["foo"]> {
    throw "Should not be called";
  }

@RyanCavanaugh RyanCavanaugh added the Working as Intended The behavior described is the intended behavior; this is not a bug label Oct 7, 2022
@RyanCavanaugh
Copy link
Member

A function consisting only of a throw statement is given type void for exactly the scenario of a psuedo-abstract base class method. If we did what you propose

Body that throws is inferred to have type never and never <: any so there should be no errors.

then SomeInterfaceStub would have an error, because string is not a subtype of never.

@thedrlambda
Copy link
Author

But surely the function, being inherited from an interface, should preserve the inherited type. I'm not saying you should give the function return type never, I'm suggesting that the body is inferred to have type never, and the function keeps its inherited type, which should type check because never is a subtype of anything.

@fatcerberus
Copy link

But surely the function, being inherited from an interface, should preserve the inherited type.

Method argument and return types are not inferred through inheritance. See #32082 for that.

@typescript-bot
Copy link
Collaborator

This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

@RyanCavanaugh
Copy link
Member

and the function keeps its inherited type, which should type check because never is a subtype of anything.

It's not legal to return a value from a function that's supposed to be of typed never

@fatcerberus
Copy link

@RyanCavanaugh I think they meant the inherited method should be inferred as returning string | undefined (its return type in the base interface), for which a lone throw would be a valid implementation.

@RyanCavanaugh
Copy link
Member

Ah, right. Of course, that just creates a different problem:

interface Foo { bar(): string | undefined }
class MyFoo() implements Foo {
  bar() {
    return "hello, world";
  }
}
const m = new MyFoo();
// m.bar() is possibly undefined -- no it isn't??
const s: string = m.bar();

@thedrlambda
Copy link
Author

I don't see the problem in your last comment. m.bar() is possibly undefined. Particularly if you use another implementing class than MyFoo. In my original code, I expect:

SomeInterface.foo: string | undefined
SomeInterfaceDummy.foo: string | undefined
SomeInterfaceDummy.foo.body: never (<: string | undefined)
SomeInterfaceStub.foo: string | undefined
SomeInterfaceDummy.foo.body: string (<: string | undefined)

Thus it should all type check.

@RyanCavanaugh
Copy link
Member

I don't see the problem in your last comment. m.bar() is possibly undefined

No, it isn't. Because m has type MyFoo, and MyFoo.bar never returns undefined.

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

4 participants