-
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
User-defined type assertions #17760
Comments
I like the idea. I've needed something like this before, and the way I've done it is by immediately throwing if the relevant type guard returns function unsafeNarrow<T>(x: any): x is T {
return true;
}
const obj = {height: 42}; // obj is {height: number}
if (!unsafeNarrow<{ name: string }>(obj)) throw null;
// obj is {height: number} & {name: string} from here on
obj.name = 'mike'; // no error It would be nice to have something less ugly. Not sure if the |
@jcalz I agree, I'm open to any syntax, that was just a suggestion as a starting point. Another really annoying thing is in tests, where you first assert that an object is truthy and then you have to explicitly check for it with an // obj comes from some test invocation
assert(obj);
// we already know that obj is not undefined but
if (obj) {
assert.equal(obj.field, "foo");
} |
also related to #10421 |
I was just looking for something like this as well. I agree with @jcalz that it seems like the existing syntax should continue to work and it's just the compiler code analysis can take into account that the failure in the function means the code throws and exits and a non-failure confirms the assertion. |
@TazmanianD I agree, we don't necessarily need new syntax, it was just nice to know by just looking at the function signature that I don't have to check for a return value. |
I was just looking for something like this as well. I don't think you can use the same I'm also not sure that the original return value // where do I write an explicit return type?
function initializeAndGetId<T>(obj: T): obj as T&{id: number} {
(obj as any).id = generateId();
return obj.id;
}
const value = {};
const id = initializeAndGetId(value);
// this should compile since value should now be assumed to have the type {id: number}
assert(id === value.id); Unfortunately, I don't really have any better suggestions. |
There are a number of ES built-in functions that would greatly benefit from this like function method(param: any) {
const myNumber: number = Number.isFinite(param) ? param : 5
} Instead, you must do something like: function method(param: any) {
const myNumber: number = (typeof param === 'number' && Number.isFinite(param)) ? param : 5
} |
@MicahZoltu In that case it's because |
Adding |
@MicahZoltu that makes sense, I didn't think about the side effects. |
I'm also running into a similar situation to @maghis in tests as #17760 (comment). I'd like for let callbackParam: string | undefined;
doesThisCallTheCallback((p) => callbackParam = p);
if (callbackParam == null) { // Want this to be in assert.isDefined()
throw new Error("TypeScript knows callbackParam is string if this hasn't thrown yet");
}
assert.isDefined(callbackParam); // throws if undefined
assert.equals(callbackParam.split(' ').length, 5); // Should know callbackParam is a string here |
@aciccarello FYI that behavior is tracked at #10421 / #8655 |
@RyanCavanaugh Issue #8655 was closed without good explanation. Many questions in the thread are still without answers. Suggested |
Testing with chai is another obvious place where this would be nice: import { expect } from 'chai';
it('should do the thing', function() {
const result = doTheThing();
expect(result).to.exist;
expect(result!.foo).to.equal(7); // I don't want to type this !. :P
}); I feel like there should be some sort of "condtional type" syntax that could be used here. Something like: function assertNotUndefined(x: string | undefined) : x is undefined ? true : never {
if(x === undefined) {
throw new Error('undefined');
}
return true;
} |
In order to meet our use case, user-defined type assertions would have to be intersectable with normal return values. In the below, I use this syntax instead of For example, suppose that you have a UI framework where children only have It would be really helpful if we could plug in to the same sort of flow analysis used by type guards. Example of desired behavior using // adding a button to a container gives it a 'layout' property
const button = new Button();
button.layout; // error because we don't know if it is added to a container yet
if (shouldAdd) {
container.addChild(button);
button.layout; // ok because of the `Ensures` type (defined below)
}
else {
button.layout; // error because control flow didn't hit anything that ensures `button.layout` defined
}
// definitions
class Component { /* ... */ }
class Container extends Component {
children: Component[] = [];
addChild<T extends Component>(child: T): this & Ensures<T is T & { layout: Layout }> {
this.children.push(child)
}
} |
This should probably be closed now that #32695 has been merged (although that PR still doesn't cover the request in the comment immediately above, to be able to return a normal value and assert with the same function). |
I think so, |
Looks like this was fixed in #32695 |
Proposal: support user-defined type assertions (keyword
as
) in addition to user-defined type guards (keywordis
).A user-defined type assertion is a function with a return type expressed as in the following example (continuing the handbook example in "Advanced Types").
:pet as fish
means that, if the function assertIsFish returns without throwing, pet is automatically asserted to be of typeFish
.Use-cases:
Validators: user-defined type assertions make it possible to encapsulate in functions the type narrowing that is currently available through control flow analysis. They also allow types that better describe the behavior of many existing libraries, including: node asserts, data validators that throw detailed errors, many test frameworks.
Polyfills: enables describing functions that modify objects by adding new properties.
Validator examples:
simple undefined checking
joi-like validation currently requires implementing the schema description of an object for runtime checking and copying the schema to an interface to have static type checking while the static type could be inferred by the implementation of the schema
The text was updated successfully, but these errors were encountered: