TypeScript Version:  master (2deb318)
Search Terms: conditional type variable parameter undefined null empty object constraint
Code
// With --strictNullChecks
type Test1 = [unknown] extends [{}] ? true : false;  // false
type IsDefinitelyDefined<T extends unknown> = [T] extends [{}] ? true : false;
type Test2 = IsDefinitelyDefined<unknown>;  // true, should be false
function oops<T extends unknown>(arg: T): {} {
    return arg;  // no error, should be an error (unknown not assignable to {})
}
console.log(oops(null).constructor);  // bang!
Expected behavior / Actual behavior: as marked
Playground Link: link (remember to enable strictNullChecks)
Related Issues: didn't find any
Discovered via https://stackoverflow.com/questions/52105268 .
In the early days before --strictNullChecks, the {} type was our top type. With --strictNullChecks we carved out null and undefined and effectively our top type then became {} | null | undefined. However, for reasons of backward compatibility we kept {} as the default inference for an unconstrained type parameter and we kept the rule that allows an unconstrained type parameter to be assigned to {}.
We now have a proper top type called unknown and if it wasn't for backward compatibility we'd switch from {} to unknown in the situations above. However, it would be a significant breaking change.
I would happily take the breaking change (under a new strict flag) for the soundness; in fact, I assumed it had come as part of --strictNullChecks until I discovered this issue.  (What was the specific reason that the change was unacceptable to make as part of --strictNullChecks, which was a large breaking change already?)
If you don't want to do that, we could at least make a type parameter explicitly constrained by unknown not assignable to {}; nobody should be relying on that behavior.  If we do that and then people use a lint rule to require every type parameter in their code to have a constraint (either unknown, {}, or something else), then we'll be in pretty good shape; the standard library may still have legacy unconstrained type parameters, but it seems unlikely to me that any of them will expose the buggy behavior.  Shall I write the PR for the assignability change?
Per notes in #26954, committed to trying it, at least
The original issue here is actually not controversial. A type parameter with an _explicit_ constraint of unknown shouldn't be assignable to {}. We should just fix that.
The larger issue is whether it is possible for us to switch the default constraint for type parameters to unknown as we discussed in #26954. We should run that experiment and discuss.
So, is it planned for next release ?
Most helpful comment
I would happily take the breaking change (under a new strict flag) for the soundness; in fact, I assumed it had come as part of
--strictNullChecksuntil I discovered this issue. (What was the specific reason that the change was unacceptable to make as part of--strictNullChecks, which was a large breaking change already?)If you don't want to do that, we could at least make a type parameter explicitly constrained by
unknownnot assignable to{}; nobody should be relying on that behavior. If we do that and then people use a lint rule to require every type parameter in their code to have a constraint (eitherunknown,{}, or something else), then we'll be in pretty good shape; the standard library may still have legacy unconstrained type parameters, but it seems unlikely to me that any of them will expose the buggy behavior. Shall I write the PR for the assignability change?