TypeScript: Assertion functions don't work when defined as methods
TypeScript Version:
3.9.0-dev.20200220
Search Terms:
assertion signature
Code
type Constructor<T> = new (...args: any[]) => T
let str: any = 'foo'
str.toUpperCase() // str is any
function assert(condition: unknown, message: string = 'Assertion failure', ErrorConstructor: Constructor<Error> = AssertionError): asserts condition {
if(!condition) {
throw new ErrorConstructor(message);
}
}
assert(typeof str == 'string')
str.toUpperCase() // str is string
class AssertionError extends Error { }
class Assertion {
assert(condition: unknown, message: string = 'Assertion failure', ErrorConstructor: Constructor<Error> = AssertionError): asserts condition {
if(condition) {
throw new ErrorConstructor(message);
}
}
}
let azzert = new Assertion().assert
let str2: any = 'bar'
azzert(typeof str2 == 'string') //error 2775
str2.toUpperCase() // str2 is any
Expected behavior:
Assertion functions are usable as methods of a class
Actual behavior:
Assertions require every name in the call target to be declared with an explicit type annotation.(2775) ‘azzert’ needs an explicit type annotation.
Playground Link:
Related Issues:
Perhaps this a design limitation per the following PR?
https://github.com/microsoft/TypeScript/pull/33622 https://github.com/microsoft/TypeScript/pull/33622#issuecomment-575301357
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Reactions: 31
- Comments: 36 (2 by maintainers)
Commits related to this issue
- * glob needs adding as a dependency * jest is no longer needed * remove ts-check for now until https://github.com/microsoft/TypeScript/issues/36931 or we migrate tests to typescript — committed to styled-components/vscode-styled-components by jasonwilliams 4 years ago
- * glob needs adding as a dependency * jest is no longer needed * remove ts-check for now until https://github.com/microsoft/TypeScript/issues/36931 or we migrate tests to typescript — committed to happydev1237/styled_components by happydev1237 4 years ago
Assertion functions always need explicit annotations. You can write this:
@justinmchase I think the part that is boggling your mind is that the message isn’t clear about what exactly doesn’t have an explicit type. You quoted this code, which clearly does have an explicit type:
But when you used it, you imported it like this:
The 2775 error you are getting on that line is not about
exists
needing an explicit type, it’s aboutassert
needing one. You can verify this by importing exists directly:So the error isn’t telling you about problems with exists, it’s telling you that your default export needs to be explicitly typed:
It isn’t entirely clear to me why that is the case, and it seems that making the error message clearer about where exactly the problem lies was discussed and rejected.
What I have found can work to avoid having to repeat the typings twice is to move the actual assertion functions into a separate file and then re-export them:
I’m also not entirely clear on why this works, but it seems like it does…
Without being too repetitive, the following works for me:
@justinmchase explicitly give it a type where you imported it.
As a prime example where you would expect this to work, but it doesn’t—in a way that seems to me at least to pretty clearly be a bug—is namespaces.
For backwards compatibility, Ember’s types have both modern module imports and re-exports from the
Ember
namespace. So we have:And we also re-export that on the
Ember
namespace, with a re-export like this:I understand the constraints as specified, and I’m afraid I must disagree with the team’s conclusion about this error message—
—seeing as it took me about half an hour to finally find my way to this thread and understand the problem, and another half an hour to figure out how to rewrite our DefinitelyTyped tests to continue handling this! 😅
For the record, if anyone else is curious, this is how I’m ending up working around it for the types PR I’ll be pushing up shortly:
(I’m also going to have to document that or be ready to explain it, though gladly basically none of our TS users are likely to be doing this.)
this is working for me (node 14, ts 4.1.3):
now you can call without having ts(2775):
I think everybody would prefer that
asserts
andis
behave the same in the context of they definition. The current situation:The current restriction is highly unintuitive and reduces
ts
community ability to create niceassert
method for schema dependent parsers/validators.For example we could have
assert
method inzod
library structs and typeguardunknown
data like this:While I can at least understand the decision to consider this working as intended, the error message you get only makes sense after you discover the solution to the problem, which is not an ideal trait in an error message.
Ok, one last time, I swear I’m not being intentionally obtuse but I think I’m homing in.
I think my confusion is because, to my understanding, this function has an explicit type:
So when you say the type needs to be explicit its boggling my mind because I don’t get how it couldn’t be explicit.
But now, I’m wondering if you don’t mean the function’s type but the type of the argument needs to be explicit? As in the
value: unknown
is the issue? It needs to be non-generic and not something such asany
orunknown
? Is that what you mean?I am also seeing error
ts(2775)
when usingrequire
instead ofimport
Following has error:
Following has no error
Troubleshoot Info
I wonder the reasons for it. I think the compiler infers a function/calling signature type rather than an “assertion function type”. It explains stuff like this:
For what it’s worth this has made the Assert library usable again when checking JavaScript with Typescript.
All the type errors reported about non-explicit types are gone (thankfully).
The bulk “Close” action doesn’t let you pick which “Close” you’re doing.
Design Limitations aren’t supposed to be left open - it means we’re not aware of any plausible way to address them.
Fwiw, if
t.assertSomething(val)
reports that error becauset
is not explicitly typed, you can solve it by doingso that
t2
is explicitly annotated as having its own type.Thanks for the clear explanation, this is a strange behavior from typescript. Got stumped by this error today.
Just throwing in my two-cents because I got struck by this annoying and very unhelpful bug wording today…
My ‘branding’ string code.
Do I really have to define all the types of this function twice just because I want to use a generic in my assertion?
If you don’t
const assertIsOneOfType: AssertsIsOneOfType
like this then I get the:Edit: Even then its not narrowing the type like it would in a regular boolean style type guard. But this works:
Edit: I see its an issue with the
const
if I just declare it is afunction
I can type it inline:@RyanCavanaugh Can you explain in a little more detail? I don’t understand how to resolve the issue.
This also is failing with the same error:
Using:
How does the exists function not have an explicit annotation? Or what can I do to resolve it in this case.
If I just export the function as non-default and do the import as
import { exists } from "./assert"
then it works but I’d like to have them grouped on the objectassert.xyz
.