TypeScript Version: [email protected], 3.5.1
Search Terms: generic bounds toString
Code
function f1<T>(o: T) {
o.toString(); // error
// This is expected, because T might be undefined or null
}
function f2<T extends {}>(o: T) {
o.toString(); // no error
// This is expected, this is the correct way to fix the above function.
}
function user<T>(t: T) {
f1(t); // Allowed, but irrelevant because f1() is invalid
f2(t); // Allowed? <- this is the bug
t.toString(); // Error, expected for the same reason as f1()
}
Expected behavior:
You should not be able to pass an arbitrary T into a function that demands T extends {}.
Actual behavior:
Despite .toString() being disallowed both in user() and in f1(), you can just pass it to f2() and still call toString on the same value.
Playground Link:
http://www.typescriptlang.org/play/#code/GYVwdgxgLglg9mABMAjAHgCoD4AUcBciGAlIgN4BQiicAdFHAMpQBOMYA5jsQNwUC+FCqEiwEyAEyZEAUwAeUGWAAmAZ3L9cBIqUrU6DZm07c+g4eGjwkIVTJaZcUQiXJVkKHFF7vgErz7UUPRMrOxcPuZAA
Related Issues:
Perhaps a simpler expression of this is
function user<T>(t: T) {
let o: {} = t;
}
Which I also expect should fail.
@weswigham ?
Is an old special assignability rule case left over from when the default for an unconstrained type parameter was {} instead of unknown, I believe. I think it's just a matter of deleting these lines:
if (!constraint || (source.flags & TypeFlags.TypeParameter && constraint.flags & TypeFlags.Any)) {
// A type variable with no constraint is not related to the non-primitive object type.
if (result = isRelatedTo(emptyObjectType, extractTypesOfKind(target, ~TypeFlags.NonPrimitive))) {
errorInfo = saveErrorInfo;
return result;
}
}
in checker.ts - but the impact of doing so will need to be checked, ofc.
Most helpful comment
Is an old special assignability rule case left over from when the default for an unconstrained type parameter was
{}instead ofunknown, I believe. I think it's just a matter of deleting these lines:in
checker.ts- but the impact of doing so will need to be checked, ofc.