-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
Alias type for indexing a generic object with with transform breaking change in 4.2+ (2nd issue) #44095
Comments
So basically you have a higher-order operation that we happen to know doesn't introduce new values, but TS doesn't really have any way to verify that, so rejects that as a lookup since there's no way to establish the symmetry of those two types. I'm not sure why this ever worked. Even staring at the code I can't tell what the sample is trying to accomplish. If you have some examples of what valid/invalid calls to getFromPropsNotShared and what the return types are supposed to be, I can probably give you a definition that works without error. |
@RyanCavanaugh Hmm
That would be very helpful! Here's a longer example which may make more sense: DescriptionBasically the The similarity is between in the OP β― Playground Linkπ» Codetype ValOf<T> = T[keyof T];
type ErrorRecord = Record<string, Error>;
interface Error {
code: number;
}
/**
* Any extra errors in the Errors constructor will be added to these, and
* anything with the same property name will override what's here.
*/
const baseErrors = {
widgetsNotWidgetyEnough: {
code: 111,
},
} as const;
/**
* Manages a record of combined error names -> codes and other metadata.
*/
export class Errors<ExtraErrors extends ErrorRecord> {
private _errorRecord: CustomObjectAssign<typeof baseErrors, ExtraErrors>;
constructor(extraErrors: ExtraErrors) {
this._errorRecord = customObjectAssign(baseErrors, extraErrors);
}
public getError(
errorName: keyof Errors<ExtraErrors>["_errorRecord"]
): ValOf<Errors<ExtraErrors>["_errorRecord"]> {
/**
* Gives a type error. (is the main problem).
*/
return this._errorRecord[errorName];
}
}
// add some extra errors or override base ones
const errors = new Errors({
widgetsNotWidgetyEnough: {
code: 114 as const,
},
tooManyDoodads: {
code: 112 as const,
},
});
// example usages
const widgetsErr = errors.getError("widgetsNotWidgetyEnough"); // should succeed
const doodadsErr = errors.getError("tooManyDoodads");
const gearsErr = errors.getError("needMoreGears"); // should error because this error name is not in either the common or the extra errors
/**
* The return type is similar to the built in return type of Object.assign,
* except that identical properties in the RHS override the LHS instead of
* producing never. Was made because it's closer to how Object.assign actually
* works.
*/
function customObjectAssign<Target extends {}, Source extends {}>(
target: Target,
source: Source
): CustomObjectAssign<Target, Source> {
return Object.assign({ ...target }, source) as unknown as CustomObjectAssign<
Target,
Source
>;
}
type CustomObjectAssign<LHS extends {}, RHS extends {}> = Omit<RHS, keyof LHS> &
{
[K in keyof LHS]: K extends keyof RHS ? RHS[K] : LHS[K];
}; |
@RyanCavanaugh made the issue in OP more specific and fleshed out the types a bit more. Felt like it was different enough to open a new one: #44108 |
A minimal repo: type PropsNotShared<LHS extends {}, RHS extends {}> =
& Omit<RHS, keyof LHS>
& Omit<LHS, keyof RHS>
;
function test<T>() {
type T1 = PropsNotShared<{x: 1}, T>;
type T2 = PropsNotShared<{x: 1}, T>;
type N1 = T1[keyof T1];
type N2 = T2[keyof T2];
var x: N1 = {} as any;
var y: N2 = {} as any;
x = y; // <- error here.
} The definition of Edit: function test<T>() {
type T3 = T & (T extends any ? {} : {});
type T4 = T & (T extends any ? {} : {});
type N3 = T3[keyof T3];
type N4 = T4[keyof T4];
var x3: N3 = {} as any;
var x4: N4 = {} as any;
x3 = x4; // <- error here.
} |
Nice! |
hmm, you think so? function test<T>() {
type T3 = (T extends any ? {} : {});
type T4 = (T extends any ? {} : {});
type N3 = T3[keyof T3];
type N4 = T4[keyof T4];
var x3: N3 = {} as any;
var x4: N4 = {} as any;
x3 = x4; // <- no error anymore.
} ...but have just an intersection and it fails again: function test<T extends {}, U extends {}>() {
type T3 = T & U;
type T4 = T & U;
type N3 = T3[keyof T3];
type N4 = T4[keyof T4];
var x3: N3 = {} as any;
var x4: N4 = {} as any;
x3 = x4; // <- error here
} And there's an intersection in the OP too, perhaps it's caused by intersections. |
It seems that this is caused by #30769. We know However, when |
@whzx5byb Thanks for tracking that down! It's very informative. Given that, and the more I think about this, I confess that I may have made a mistake. So that PR is saying that when T[K] is on the target side of a type relationship it's the intersection of the types of the values of T. But you know, that makes sense. After all, if all you know about K is that it is any of the keys of T, and any of these T[K] have incompatible types, then it shouldn't be possible to assign to them, right? So in that case an intersection makes never which can't be assigned to. like in here: type PropsNotShared<LHS extends {}, RHS extends {}> =
& Omit<RHS, keyof LHS>
& Omit<LHS, keyof RHS>
;
function test<T>() {
type T1 = PropsNotShared<{x: 1}, T>;
type T2 = PropsNotShared<{x: 1}, T>;
type N1 = T1[keyof T1];
type N2 = T2[keyof T2];
var x: N1 = {} as any;
var y: N2 = {} as any;
x = y; // <- error here.
} All typescript knows about x and y is that they are some value of T1 (or T2 doesn't matter). But all of those values are not neccessarily assignable to each other, hence the error. In fact, i'm kind've swinging the opposite direction now, and think that it should fail more than it does. Consider this example which should fail but doesn't. function test<T>() {
var x: T[keyof T] = {} as any;
var y: T[keyof T] = {} as any;
x = y; // <- should error here, because not all values of T are necessarily assignable to each other
} The nice thing about PropsNotShared was that it was a longish expression which could be deconstructed by typescript, and so if you were assigning an index of it to another index of it, the target type i.e. intersection of all possible values actually looked to be a different expression than the source type i.e. the union of all possible values. But above, no information is known about |
I'd be willing to close this if @whzx5byb doesn't have any more issues. |
Bug Report
π Search Terms
index generic with alias, alias generic
π Version & Regression Information
β― Playground Link
Playground link
π» Code
π Actual behavior
Says the line
return notShared[prop];
ingetFromPropsNotShared()
is not assignable to the annotated return type. Works if you replace it with a similar annotation manually resolving ValOf. But it worked in 4.1 and it seems to me like the two different return type expressions should mean the same (see comment).π Expected behavior
Would expect the line
return notShared[prop];
to succeed with the ValOf<...> return type ongetFromPropsNotShared()
.The text was updated successfully, but these errors were encountered: