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

Non‑void returning assertion functions #40562

Open
5 tasks done
ExE-Boss opened this issue Sep 14, 2020 · 6 comments
Open
5 tasks done

Non‑void returning assertion functions #40562

ExE-Boss opened this issue Sep 14, 2020 · 6 comments
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript

Comments

@ExE-Boss
Copy link
Contributor

ExE-Boss commented Sep 14, 2020

Search Terms

  • assertion function non-void return type
  • assertion function non-void return
  • assertion function generic return
  • assertion function custom return

Suggestion

A way to type a function that is both an assertion function and returning a value that is not void.

Use Cases

This is necessary to correctly type Jest’s expect(…) matchers, which have a generic return type of R.

Examples

declare const expect: <T>(actual: T): JestMatchers<T>;

type JestMatchers<T> = JestMatchersShape<
	Matchers<void, T>,
	Matchers<
		Promise<void>,
		T extends PromiseLike<infer U>
			? U
			: Exclude<T, PromiseLike<any>>
	>
>;

type JestMatchersShape<TNonPromise extends {} = {}, TPromise extends {} = {}> = {
	resolves: AndNot<TPromise>;
	rejects: AndNot<TPromise>;
} & AndNot<TNonPromise>

type AndNot<T> = T & { not: T };

interface Matchers<R, T = {}> {
	toBe<E>(expected: E): R & (asserts T is E);
}

declare const foo: unknown;
expect(foo).toBe("foo");
foo; // $ExpectType "foo"
// Some typings for engine262's Completion Record handling:
type UnwrapNormalCompletion<T>
	= unknown extends T ? Value | undefined
	: T extends NormalCompletion<infer V>
		? (unknown extends V ? Value | undefined : V)
	: T extends Completion ? never
	: T;

/** @see https://tc39.es/ecma262/#sec-returnifabrupt */
export declare function ReturnIfAbrupt<T>(completion: T):
	(UnwrapNormalCompletion<T>)
	& (asserts completion is UnwrapNormalCompletion<T>);

/** @see https://tc39.es/ecma262/#sec-returnifabrupt-shorthands */
export { ReturnIfAbrupt as Q };

/**
 * The type signature is the same, but `AssertNormalCompletion` causes an error to be thrown at runtime
 * if `argument` is an AbruptCompletion, whereas `ReturnIfAbrupt` gets replaced with code that causes
 * the caller to return the AbruptCompletion by engine262's build system:
 *
 * @see https://tc39.es/ecma262/#sec-returnifabrupt
 * @see https://tc39.es/ecma262/#sec-returnifabrupt-shorthands
 */
declare function AssertNormalCompletion<T>(completion: T):
	(UnwrapNormalCompletion<T>)
	& (asserts completion is UnwrapNormalCompletion<T>);
export { AssertNormalCompletion as X };

Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.
@jcalz
Copy link
Contributor

jcalz commented Sep 15, 2020

duplicate of or related to #34636

@RyanCavanaugh RyanCavanaugh added Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript labels Sep 16, 2020
@ghost ghost mentioned this issue Feb 7, 2021
5 tasks
@ttencate
Copy link

Note that the duplicate #34636 (which got here first) has 39 👍 at the time of writing, and this one has only 9 👍. If these are used for prioritization, perhaps this one should be closed and the other should be reopened?

@RebeccaStevens
Copy link

Here's a simpler example that doesn't require any 3rd party libraries:

type ValidRawData = { foo: string; };
declare function assertIsValidRawData(value: unknown): asserts value is ValidRawData;

// Ideal return type would be some like:
// ParsedData & asserts data is ValidRawData
function parseData(data: unknown) {
    assertIsValidRawData(
        data !== null &&
        typeof data === "object" &&
        Object.hasOwn(data, "foo") &&
        typeof data.foo === "string"
    );
    ...
}

const rawData: unknown = {};
const data = parseData(rawData);
const foo = rawData.foo; // <== Not currently valid. `parseData` called above should be able to assert that rawData is of type ValidRawData.

Playground link.

@natew
Copy link

natew commented May 20, 2022

One footgun this avoids is it can avoid unused variables:

const val = api.value
ensureExists(val)
// if you never use val for rest of file, but linter is happy
const val = ensureExists(api.value)
// if you never use val for rest of file, linter will complain / editor will show unused

@RebeccaStevens
Copy link

@natew If you never use val, why define it? just call the function.

ensureExists(api.value)

@ExE-Boss ExE-Boss changed the title Non‑void returning assertion functions Non‑void returning assertion functions May 21, 2022
@natew
Copy link

natew commented May 21, 2022 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

6 participants