-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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
Nested conditional generic types yields boolean not assignable to true | false | undefined. #22630
Comments
I've gotten a similar error but with an object instead. interface Stamp {
id: number
imageUrl: string
/** Hide from the list of new stamps. */
hidden: boolean
}
type StampOptions = Pick<Stamp, 'hidden'>
const defaultOptions: StampOptions = {
hidden: false
}
// The | StampOptions is something similar to React.Component#setState from @types/react
declare function makeStamp<Keys extends keyof StampOptions> (id: number, options?: Pick<StampOptions, Keys>|StampOptions): Stamp
function makeStampSeries<Keys extends keyof StampOptions> (seriesId: number, options?: Pick<StampOptions, Keys>|StampOptions, count = 10): ReadonlyArray<Stamp> {
return Array.from({ length: count }, (_, i) => makeStamp(seriesId * 100 + i + 1, options))
}
This |
@Griffork I'm not seeing any errors with your example. |
@ahejlsberg My apologies, in my rush I missed one important part of the code, here's the updated sample: type RecursivePartial<T> = { [param in keyof T]?: T[param] extends object ? RecursivePartial<T[param]> : T[param] };
interface UpdateValue<T> {
$set: T extends object? RecursivePartial<T>: T;
}
interface ArrayUpdate<T> {
$wildcard: WriteQuery<T>;
}
export type TypeWriteQuery<T> =
T extends Array<infer U>
?
Partial<ArrayUpdate<U>>
:
T extends object
?
WriteQuery<T>
:
Partial<UpdateValue<T>>
;
export type WriteQuery<T> = { [param in keyof T]?: TypeWriteQuery<T[param]> };
export async function Send<T>(Url: string, write?: WriteQuery<T>, options?: any): Promise<T[]> {
return [];
}
///----- Erroring code:
interface Approved {
approved: boolean;
}
Send<Approved>("doot",
{ approved: { $set: <boolean>true } }); It needed the object literal to be passed into the function call for it to work. Edit: Casting the object literal directly to the type expected by the function |
Best I can tell this is working as intended. The issue here is that the conditional type in |
It's intended that it errors when the type is enforced by a function but not when its cast to or assigned to that type? |
Argh, can't edit post on mobile. And it's |
@Griffork the linked issue demonstrates how to cause a conditional type to not distribute over unions |
@RyanCavanaugh I've read through the issue twice now and don't understand how anything in there solves my problem. What I have read is this:
Which seems to imply my above example is a bug, since every atomic type but boolean works this way. Also I don't understand why boolean has to expand to |
@Griffork The The issue you're seeing isn't specific to As I mentioned above, you can use the techniques in #22596 to control whether and how the |
@ahejlsberg apparently I'm missing something obvious, but when reading through #22596 I didn't see any "techniques" that solve my problem. Is it too much for me to ask for you to provide an example? I suppose I understand what you're saying that boolean should be equal to a union of literal types, but that's not what I expect to happen when I write the word boolean (as opposed to true|false). When I write boolean I expect it to behave equally to the other built-in atomic types, since it is a built-in atomic type (the fact that it happens to have only two possible values is irrelevant I feel). The other thing that feels inconsistent is that (at least in strict mode) boolean behaves differently to everything else because it's the only keyword that implies a union type. Edit: fixed typos. |
@Griffork I'd change type TypeWriteQueryDistributed<T> =
T extends Array<infer U> ? Partial<ArrayUpdate<U>> :
T extends object ? WriteQuery<T> :
T extends null | undefined ? T :
never;
type Primitives<T> = T extends object | null | undefined ? never : T;
export type TypeWriteQuery<T> =
TypeWriteQueryDistributed<T> |
(Primitives<T> extends never ? never : Partial<UpdateValue<Primitives<T>>>); |
@ahejlsberg ok that seems to work (I have another error which I suspect is unrelated). How does that work though? I don't understand why wrapping primitives in another layer of genericity helps. |
Ok, the other error was indeed unrelated. The other thing I've just noticed, which is what you were trying to tell me all along, is that fixing the boolean problem will also fix problems I would have had with trying to wrap an object that defines string or number literals, or any other union as value types, because we want them all to be treated as a group. Still don't understand why this works though. |
The Say you're resolving
Similarly So, you end up with |
My scenario is that I can have any JSON object stored in a database, and the database has very a specific query structure. Because we don't have typing information at runtime I'm trying to get compile-time checks for query objects. interface MyFoo {
a: string;
b: boolean;
c: {
d: boolean;
e: string;
x: string[];
y: boolean[];
z: {
i: boolean;
j: string;
}[];
};
f: <boolean[];
g: string[]
h: {
i: boolean;
j: string;
}[];
} Needs to become: interface MyFooQuery {
a?: UpdateValue<string>;
b?: UpdateValue<boolean>;
c?: UpdateValue<{
d: boolean;
e: string;
x: string[];
y: boolean[];
z: {
i: boolean;
j: string;
}[];
}> |
{ //And so-on recursively
d?: UpdateValue<boolean>;
e?: UpdateValue<string>;
x?: ArrayUpdate<string>;
y?: ArrayUpdate<boolean>;
z?: ArrayUpdate<{
i: boolean;
j: string;
}>;
};
f?: ArrayUpdate<boolean>;
g?: ArrayUpdate<string>;
h?: ArrayUpdate<{
i: boolean;
j: string;
}>;
} Naturally, I don't want to have to manually type every single interface, since there's a lot of them. Potential problem area: interface ArrayUpdate<T> {
$set: T[];
} The thing is, that var test: ArrayUpdate<string | number> = {
$set: ["toot", 3]
} And the other one only permits this: var test2: ArrayUpdate<string>| ArrayUpdate<number> = {
$set: ["toot", "anothertoot"]
};
//or
var test3: ArrayUpdate<string>| ArrayUpdate<number> = {
$set: [2, 3]
}; but this errors: var test4: ArrayUpdate<string>| ArrayUpdate<number> = {
$set: ["toot", 3]
}; |
@ahejlsberg does that clear my use-case up a bit or would you like me to provide more information? |
No, I was just saying that I couldn't be sure exactly what your intent was. Hopefully the explanation I provided above gives you a better understanding of how distributive conditional types work so you can find the solution that best works for you. |
@ahejlsberg you said:
But as explained above what I want is Edit: looking at the linked issue it looks like you're thinking of proposing a new syntax for this. I'm asking if there's any way I can rewrite my types to do it now, since I'd ideally like to do this at work within the month (and I can't use nightlys on a live work project). |
Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed. |
TypeScript Version: 2.8RC
Search Terms:
Code
End of friday, so I don't have enough time to make a proper repro, hopefully this will be enough to go by:
Expected behavior:
Works fine (unless I've made another mistake).
Actual behavior:
Errors that
boolean
isn't assignable totrue | false | undefined
becauseboolean
isn't assignable tofalse
.The text was updated successfully, but these errors were encountered: