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

Cannot invoke an expression whose type lacks a call signature when --strictNullChecks enabled #14091

Closed
punmechanic opened this issue Feb 15, 2017 · 5 comments
Labels
Fixed A PR has been merged for this issue Suggestion An idea for TypeScript

Comments

@punmechanic
Copy link

punmechanic commented Feb 15, 2017

TypeScript Version: 2.1.5 and 2.1.6

Code

With --strictNullChecks enabled.

interface Foo {
  foo(): void
}

class A<P extends Partial<Foo>> {
  props: Readonly<P>
  doSomething() {
    this.props.foo && this.props.foo()
  }
}

Expected behavior:
I should be able to invoke this.props.foo() as I have proven to the typechecker that this.props.foo is not undefined† and it cannot be any other value.

Actual behavior:
Throws the following compiler error:

test.ts(12,23): error TS2349: Cannot invoke an expression whose type lacks a call signature. Type '(() => void) | undefined' has no compatible call signatures.

This issue was introduced in TypeScript 2.1.5 and is present in TypeScript 2.1.6. The above code compiles just fine in TypeScript 2.1.4.

This issue does not present itself if A.props is P instead of Readonly<P>. You are unable to circumvent this error by using !, so this is probably not an error with null checking, despite it only being present while strictNullChecks are enabled.

Without wanting to make myself sound more intelligent than I am, I suspect the issue has something to do with the interaction between readonly and a potentially undefined value.

† Technically this code proves to the typechecker that this.props.foo is not falsey, however the same issue is found when comparing this.props.foo explicitly to undefined or doing typeof this.props.foo === 'function'.

@mhegazy
Copy link
Contributor

mhegazy commented Feb 15, 2017

Since there is a generic type, the type of this.props.foo is P["foo"]. and currently narrowing does not work on generic types. what we need is a new type operator, call it !, so that the type of this.props.foo after the check would be P["foo"]!, which is guaranteed to not be undefined or null.

@mhegazy mhegazy added Suggestion An idea for TypeScript In Discussion Not yet reached consensus labels Feb 15, 2017
@punmechanic
Copy link
Author

punmechanic commented Feb 15, 2017

Hey @mhegazy, regardless of discussions of feature implementations - I submitted this as a bug because it's a regression; this was working perfectly fine in TS 2.1.4. What has changed?

I'd also be surprised if we would want a new operator for this; this seems a very intuitive type deduction for TypeScript to be able to make. I wouldn't want to have to circumvent the type checker by forcing it to ignore the possibility of something being undefined.

In general I try to tell the compiler what something because I'm usually wrong, so I would be pretty upset if the answer to this was this.props.foo()! :(

Also worth noting that the above code works perfectly fine without the Readonly<T> type.

@JabX
Copy link

JabX commented Feb 16, 2017

I'm pretty sure that I have the same problem with the newest React definitions that define this.props as Readonly<P>. A lot of my code breaks because narrowing from P["foo"] doesn't work and I'm not ready to rewrite some perfectly valid code, so I'm sticking with the older definitions where this.props is mutable.

@punmechanic
Copy link
Author

@JabX Yes, this is where I got the issue from. I just simplified the example into something that was, well, simpler.

@vegansk
Copy link

vegansk commented Apr 21, 2017

So, is this the only workaround or it can be prettier?

export interface FormBaseProps {
  onInitForm?: (f: FormBase<any, any>) => any
  onDestroyForm?: (f: FormBase<any, any>) => any
}
abstract class FormBase<T extends FormBaseProps, S> extends Component<T, S> {
  componentWillMount() {
    this.props.onInitForm && (this.props as FormBaseProps).onInitForm!(this)
  }

  componentDidUnmount() {
    this.props.onDestroyForm && (this.props as FormBaseProps).onDestroyForm!(this)
  }
}

@mhegazy mhegazy added Fixed A PR has been merged for this issue and removed In Discussion Not yet reached consensus labels May 8, 2017
@mhegazy mhegazy added this to the TypeScript 2.4 milestone May 8, 2017
@microsoft microsoft locked and limited conversation to collaborators Jun 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Fixed A PR has been merged for this issue Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

4 participants