You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Constraint failing for subtype (regression) #29464
Changing the properties to string will not work; you're intuition about the extends is correct.
The constraint extends { [key: string]: string } says that whatever type Base gets selected to be, it must be at least as specific as { [key: string]: string }, but it could be more specific. For example, you could instantiate Base with a type that has known properties of literal type, such as:
typeBase={x: "x"};
Similarly, because the constraint is only an upper bound, the only thing you can say about Base[Key] is that it is at least as specific as string, but it could be more specific. That's why you cant say for certain that its OK to instantiate it to string, it could be the case that Base[Key] = "x", which would then give an error.
The reason that your later example works is because you are not instantiating func1 with other generic parameters, rather, concrete types. Here you no longer need to work in bounds, and get determine everything precisely.
func1<myInterface1,string,keyofmyInterface1>
myInterface1, string, and keyof myInterface1 are all known types. At the call site of func1 you have instantiated Key to be keyof myInterface1, which is equal to "X" | "Y". Whatever you pick for attrName will always satisfy this, because you know that attrName, in the worst case, will have type "X" | "Y". If it is more specific you're still good.
Basically the difference boils down to where generic parameters appear in an A extends B condition.
In the unsound case you have string extends Base[Key], with the generic type Base[Key] on the right hand side
In the sound case you have Key extends ("X" | "Y"), with generic type Key appearing on the left hand side.
In general you can reason about generic parameters on the left hand side of a constraint, but it's hard to reason about generic things on the right hand side.
I kind of understand, but I'm not sure how I can replace my solution with something that works on 3.2+
What I'm trying to achieve is a function that validates attribute of an object at run-time. But to be safer, I also want to validate that the parameters I give to that function matches the object property, at the compilation time:
This is a more complete example:
type TypeName<T> = T extends string
? "string"
: T extends number
? "number"
: T extends boolean
? "boolean"
: T extends undefined
? "undefined"
: T extends Function
? "function"
: T extends ArrayLike<any>
? "array"
: T extends Object
? "object"
: never;
type FilterFlags<Base, Condition> = { [Key in keyof Base]: Base[Key] extends Condition ? Key : never };
type AllowedNames<Base, Condition> = FilterFlags<Base, Condition>[keyof Base];
function validateAttribute<Base extends { [key: string]: any }, Type extends Base[Key], Key extends keyof Base>(
obj: Base,
attrName: Key,
attrType: TypeName<Type>
) {
return typeof obj[attrName] === attrType;
}
function validateString<Base extends { [key: string]: any }, Key extends AllowedNames<Base, string>>(
obj: Base,
attrName: Key
) {
validateAttribute<Base, string, Key>(obj, attrName, "string");
}
interface myInterface {
fieldString: string;
fieldNumber: number;
}
let myObj: myInterface = {
fieldString: "hello",
fieldNumber: 18
};
validateString(myObj, "fieldString"); // Should pass
validateAttribute(myObj, "fieldNumber", "number"); // Should pass
validateString(myObj, "fieldNumber"); // Should fail at compilation because fieldNumber is not a string
validateAttribute(myObj, "fieldString", "number"); // Should fail at compilation because fieldString is not a number
validateString(myObj, "unknown"); // Should fail at compilation because unknown doesn't exist
}
It worked fine with 3.1 but I don't know how to make it on 3.2
In this case it works.
In my actual code I didn't use the Record<key, T> however and kept the Base extends { [key: string]: any }
However I didn't know we could use the = like that (T = Base[Key]). It fixed my issue
Activity
jack-williams commentedon Jan 17, 2019
The access here is unsound; I think it was fixed by #27490.
Consider the instantiation:
Here the type of
Base[Key]would be"x", whichstringis not assignable to.crystalin commentedon Jan 17, 2019
What you say makes sense but then, should changing the type of the properties of Base to string should work (or is "extends" preventing it to work) ?
Also why in that case, this example work?
This could clearly have "Key" being "Y" and so the type would not be "string" but "number"
jack-williams commentedon Jan 17, 2019
Changing the properties to
stringwill not work; you're intuition about theextendsis correct.The constraint
extends { [key: string]: string }says that whatever typeBasegets selected to be, it must be at least as specific as{ [key: string]: string }, but it could be more specific. For example, you could instantiateBasewith a type that has known properties of literal type, such as:Similarly, because the constraint is only an upper bound, the only thing you can say about
Base[Key]is that it is at least as specific asstring, but it could be more specific. That's why you cant say for certain that its OK to instantiate it tostring, it could be the case thatBase[Key] = "x", which would then give an error.The reason that your later example works is because you are not instantiating
func1with other generic parameters, rather, concrete types. Here you no longer need to work in bounds, and get determine everything precisely.myInterface1,string, andkeyof myInterface1are all known types. At the call site offunc1you have instantiatedKeyto bekeyof myInterface1, which is equal to"X" | "Y". Whatever you pick forattrNamewill always satisfy this, because you know thatattrName, in the worst case, will have type"X" | "Y". If it is more specific you're still good.Basically the difference boils down to where generic parameters appear in an
A extends Bcondition.string extends Base[Key], with the generic typeBase[Key]on the right hand sideKey extends ("X" | "Y"), with generic typeKeyappearing on the left hand side.In general you can reason about generic parameters on the left hand side of a constraint, but it's hard to reason about generic things on the right hand side.
crystalin commentedon Jan 18, 2019
I kind of understand, but I'm not sure how I can replace my solution with something that works on 3.2+
What I'm trying to achieve is a function that validates attribute of an object at run-time. But to be safer, I also want to validate that the parameters I give to that function matches the object property, at the compilation time:
This is a more complete example:
It worked fine with 3.1 but I don't know how to make it on 3.2
jack-williams commentedon Jan 18, 2019
Does this work?
Not sure it's the best way though..
crystalin commentedon Jan 18, 2019
In this case it works.
In my actual code I didn't use the Record<key, T> however and kept the Base extends { [key: string]: any }
However I didn't know we could use the
=like that (T = Base[Key]). It fixed my issue