-
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
Type guard should infer the type of parent object when applied on a property #42384
Comments
Used search terms: |
@MartinJohns Thanks but I am trying to understand why. Consider this example:
So if I check with |
I want to take another look at this; seems like we might have the right machinery in place now |
I ran into this issue today, and would love to see it resolved. In my case, I'm using the excellent SuperStruct package to validate parts of an incoming HTTP request in Koa. I'm using the type guards generated by SuperStruct to validate the query parameters ( |
I ran into a similar situation today, which boiled down to (see duplicated issue): type Slot = { id?: string }
function needsId(slot: { id: string }) {
}
function test(slot: Slot) {
if (slot.id) {
needsId(slot)
}
} |
I've run into this issue too. anyone found a work-around yet? I'd really like to not have to repeat type guards all over the place |
Also having this issue. See this playground. |
Here is a workaround for my use case: function assertField<T extends { [field in Field]?: T[field] }, Field extends keyof T & string,>(
obj: T,
field: Field
): obj is T & { [field in Field]: NonNullable<T[field]> } {
if (!obj[field]) false;
return true;
}
type Slot = { id?: string }
function needsId(slot: { id: string }) {
}
function test(slot: Slot) {
if (slot.id) {
slot.id; // string | undefined
// needsId(slot) ERROR
}
if (assertField(slot, 'id')) {
slot.id; // string
needsId(slot); // WORKS
}
} |
I mentioned another workaround on a duplicate issue that I opened, so just sharing it here. For simple use cases, like checking if a single value is defined or not, there is no need to add a type guard since that would make an extra function call in runtime. Wrap the type and the property here to make it propagate: type MakeItPropagate<T, K extends keyof T> =
| (Omit<T, K> & Partial<Record<K, never>>)
| (Omit<T, K> & {
[KI in K]-?: NonNullable<T[KI]>
}); This makes transforms a record with an optional property into a union using the presence of that property as the discriminator. |
This is mentioned in #50891 already linked here, but for visibility: Now that |
@RyanCavanaugh any chance this improvement could still be achievable in the hopefully-not-too-distant future? π |
I have another reproduction but I am not entirely certain this is the same issue - here. type MyUnion = {
arr: string[],
type: 'one'
} | {
arr: number[],
type: 'two'
}
function Test(mu: MyUnion) {
// here, narrowing works:
if (mu.type === 'one') {
// arr[0] is narrowed to 'string' as expected
mu.arr[0].toLowerCase()
} else {
// arr[0] is narrowed to 'number' as expected
mu.arr[0].toExponential(2)
}
// narrowing doesn't work below:
for (const el of mu.arr) {
if (mu.type === 'one') {
// 'el' should be narrowed to 'string' here
// ... but we get TS error: "Property 'toLowerCase' does not exist on type 'string | number'"
el.toLowerCase()
} else {
// 'el' should be narrowed to 'number' here
// ... but we get TS error: "Property 'toExponential' does not exist on type 'string | number'"
el.toExponential(2)
}
}
} |
Just sharing another use-case which is for interfaces we don't control. For example the GeoJSON spec was created years before TypeScript existed. Working with GeoJSON in TypeScript is rather painful as typically the types are something like: interface Polygon {
type: "Polygon";
coordinates: number[][][];
}
interface LineString {
type: "LineString";
coordinates: number[][];
}
// ... more geometry types
type Geometry = Polygon | LineString | ...
type Feature<T extends Geometry = Geometry> = {
type: "Feature";
geometry: T;
properties: {};
} It's simply not possible to refine a |
Naturally it's less convenient than if this issue was implemented, but it's usually not too bad to write a sort of typeguard factory for cases like this. Also this usecase doesn't really need the full generality of this issue, something like #18758 would suffice. |
mmm having to end up doing a lot of type assertions to handle this... I ended up creating a type guard in case anyone wanted some inspiration or an example type Foo<T> = { inner: T }
function foo_inner_guardX, T extends X, Args extends Array<unknown> = []>(
value: Foo<X>,
pred: (inner: X, ...args: Args) => inner is T,
...args: Args
): value is Foo<T> {
return pred(value.inner, ...args)
} |
Another repro, using |
I've run into a similar issue also regarding whether properties of an object are non-nullable, see example below: const obj: {foo: string | null} = {foo: ""};
declare function f(arg: {foo: string}): void
if(obj.foo) {
f(obj) // TYPE ERROR: overall object type not narrowed to have property as non-nullable
f({foo: obj.foo}) // OK: object property types narrowed when used individually
} |
Suggestion
π Search Terms
Type guard, parent object, infer, inference
β Viability Checklist
My suggestion meets these guidelines:
β Suggestion
Typescript should be able to infer the type of an object, if a type guard was checked on a property of the said object. Currently, Typescript does correctly infer the type of the property, but not its parent object.
π Motivating Example
The following example is very common in data oriented design.
The following example works and that is good because it is an equally common case in this type of design.
So I believe the type guards should work the same way. As far as I see, this does not cause any inconsistency in the language or the type system. It is an improvement without any downsides.
π» Use Cases
See the full example.
The text was updated successfully, but these errors were encountered: