-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Description
Suggestion
Sadly, right now getConstrainedTypeOfLocation
is bugged for unconstrained generics. The code just gives back the type parameter type if getBaseConstraintOfType(nodeType) is nullish, but that's not correct. Unconstrained generics should behave like unknown
. Here's what it is right now:
/** | |
* Resolves the given node's type. Will resolve to the type's generic constraint, if it has one. | |
*/ | |
export function getConstrainedTypeAtLocation( | |
services: ParserServicesWithTypeInformation, | |
node: TSESTree.Node, | |
): ts.Type { | |
const nodeType = services.getTypeAtLocation(node); | |
const constrained = services.program | |
.getTypeChecker() | |
.getBaseConstraintOfType(nodeType); | |
return constrained ?? nodeType; | |
} |
And here's what it "should" be:
/**
* Resolves the given node's type. Will resolve to the type's generic constraint, if it has one.
*/
export function getConstrainedTypeAtLocation(
services: ParserServicesWithTypeInformation,
node: TSESTree.Node,
): ts.Type {
const nodeType = services.getTypeAtLocation(node);
if (tsutils.isTypeParameter(nodeType)) {
const checker = services.program.getTypeChecker();
return (
checker.getBaseConstraintOfType(nodeType) ?? checker.getUnknownType()
);
}
return nodeType;
}
Unfortunately, due to microsoft/TypeScript#60475, there is no checker.getUnknownType()
yet or for a while.
It kind of sucks, but I propose that we instead modify the implementation to something more like
export function getConstrainedTypeAtLocation(
services: ParserServicesWithTypeInformation,
node: TSESTree.Node,
): ts.Type | undefined { // <-- note the `| undefined`
const nodeType = services.getTypeAtLocation(node);
if (tsutils.isTypeParameter(nodeType)) {
const checker = services.program.getTypeChecker();
return checker.getBaseConstraintOfType(nodeType)
/* in the future this can have ?? checker.getUnknownType() */;
}
return nodeType;
}
and force callers to handle the unconstrained case separately.
Note - I think we should also double-check (and add tests) for what happens when the constraint is any
. I'm pretty sure function f<T extends any>(x: T) {}
behaves as though T
were unknown
rather than any
. So maybe we should really be doing
export function getConstrainedTypeAtLocation(
services: ParserServicesWithTypeInformation,
node: TSESTree.Node,
): ts.Type | undefined {
const nodeType = services.getTypeAtLocation(node);
if (tsutils.isTypeParameter(nodeType)) {
const checker = services.program.getTypeChecker();
const constraint = checker.getBaseConstraintOfType(nodeType);
return constraint == null || isTypeAnyType(constraint)
? /* in the future this can be checker.getUnknownType() */ undefined
: constraint;
}
return nodeType;
}
I haven't yet checked what checker.getBaseConstraintOfType()
does with an any
.
Additional Info
Related, caused #10311
Related, discussion at #10314 (comment)
Possibly related, #10415
cc @ronami