-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
ReturnType support for assertions #34636
Comments
This is by design, according to the PR introducing this features:
The first item states that the call must occur as a top-level statement, this means that the call can't be nested in an expression (such as a parameter to another function or in an assignment). This means that effectively, even if you could return from an assertion function, using it in a position where the return value could be use, the function would not be analyzed as an assertion but rather as a regular function call. So it is best to implicitly just say that an assertion function returns
Your first example ( Suggestion: @ahejlsberg Maybe disallow any return statement that has an expression in assertion functions? |
The real world use case outlined here is handled fine by typescript if, instead of async function f() {
const val = await p;
assert1(val);
val.length; // number
} |
@nmain There are many other use cases. Especially in arrow functions const bar = () => assert(someTask());
// instead of
const bar = () => {
const result = someTask();
assert(result);
return result;
} |
You can do that easily without getting involved with the assert feature at all: function assert<T>(v: T): NonNullable<T> {
if (v == null) {
throw null;
}
return v as any;
}
declare function someTask(): string | undefined;
const bar = () => assert(someTask()); // () => string, instead of () => string | undefined I certainly see the draw of being able to use the same |
@nmain I know. But this increase entropy - i should have two assert functions with same code; function assert(value: any): asserts value {
if (!value) {
throw new Error("Assertion failed.")
}
function assertAndReturn<T>(value: T): Exclude<T, null | undefined | void | 0 | false | ''> {
if (!value) {
throw new Error("Assertion failed.")
}
return value as any;
} It's silly IMO. And as i said before - why not to add narrowing at least? const assertAndReturn = <T>(x: T) => {
assert(x);
return x;
}
p.then((x) => { assert(x); return x; }).then((x) => x.length) // Works fine
p.then(assertAndReturn).then((x) => x.length); // Doesn't narrow @RyanCavanaugh @dragomirtitian Is it hard to add narrowing for code above? |
As a data point, both returning a value and narrowing the type of the input is also how Closure's |
I also tried to get around this by having two assert functions, one to function _assert<T = any>(x: T): asserts x { // say, private one
if (!x) { throw new Error(); }
}
function myAssert<T = any>(x: T): T { // say, public one that returns
_assert(x);
return x;
}
type Employee = { name: string; }
function getEmployee(id: number): Employee | null {
if (id !== 2) {
return null;
}
return { name: "James" };
}
const emp = getEmployee(3);
console.log(myAssert(emp).name); // Object is possibly null :(
// Pure check:
// _assert(emp); // without this line, I get "Object possibly null" in the console.log
// console.log(emp.name); But unfortunately, while |
I ran into to trying to do this today, my use case is this: interface Provider {
initiatePayment (items: Items): Promise<string>
}
function getProvider (id: string): asserts id is ('foo' | 'bar') & Provider {
switch (id) {
case 'foo': return fooProvider
case 'bar': return barProvider
default: throw new Error(`Unknown payment provider: ${id}`)
}
}
function makePurchase (provider: string, items: Items) {
const providerImpl = getProvider(provider)
const paymentId = await providerImpl.initiatePayment(items)
savePurchase({
provider, // <---- Provider needs to be typed as 'foo' | 'bar' here!
paymentId,
items
})
} |
Another datapoint: combining |
I would really love to see this happen, and for the restriction
to be made obsolete. |
@RyanCavanaugh Just checking - was this closed because it's implemented now or because it's not considered or? |
@RReverser Presumably because #40562 is a duplicate of this one. |
Hmm 1) it wasn't linked during closing and 2) it's a newer issue, so I'd expect the newer one would be closed as a duplicate... |
FWIW the specific This doesn't cover all use-cases though. |
@nightlyherb no it doesn't; that function you've written isn't returning an assertion, it's just returning a value. It's not a type guard. Here is my use case for this
If this feature were implemented in TS, I'd probably submit a contribution to the jest codebase to make certain matchers type guards. |
Having type guard matchers with the Jest matchers interface is a highly wanted feature IMO! |
That’s because your code is equivalent to: type AssertionType = { toBeUndefined: () => void }
const expect: <T>(bar: T | undefined) => asserts bar is (T & AssertionType) = (bar) => {
return {
toBeUndefined: () => {},
};
};
// Currently there is an error here
expect(foo).toBeUndefined(); What you want, and is currently impossible: type AssertionType = { toBeUndefined: () => void }
// The error is now here:
const expect: <T>(bar: T | undefined) => (asserts bar is T) & AssertionType = (bar) => {
return {
toBeUndefined: () => {},
};
};
expect(foo).toBeUndefined(); |
Suggestion
Not found a way to provide return type for assert function. Look at use case
Use Cases
This doesn't work also
Playground
Related issues
#34596
The text was updated successfully, but these errors were encountered: