Skip to content
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

unconstrained type parameters are consistently checked for {} | null | undefined in strictNullChecks #59059

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 21 additions & 8 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18299,6 +18299,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {

function shouldDeferIndexType(type: Type, indexFlags = IndexFlags.None) {
return !!(type.flags & TypeFlags.InstantiableNonPrimitive ||
type.flags & TypeFlags.Index && isUnconstrainedTypeParameter((type as IndexType).type) ||
isGenericTupleType(type) ||
isGenericMappedType(type) && (!hasDistributiveNameType(type) || getMappedTypeNameTypeKind(type) === MappedTypeNameTypeKind.Remapping) ||
type.flags & TypeFlags.Union && !(indexFlags & IndexFlags.NoReducibleCheck) && isGenericReducibleType(type) ||
Expand Down Expand Up @@ -18987,7 +18988,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
isGenericTupleType(objectType) && !indexTypeLessThan(indexType, getTotalFixedElementCount(objectType.target)) :
isGenericObjectType(objectType) && !(isTupleType(objectType) && indexTypeLessThan(indexType, getTotalFixedElementCount(objectType.target))) || isGenericReducibleType(objectType))
) {
if (objectType.flags & TypeFlags.AnyOrUnknown) {
if (objectType.flags & TypeFlags.AnyOrUnknown || (strictNullChecks && (objectType.flags & TypeFlags.Union) && isUnknownLikeUnionType(objectType))) {
return objectType;
}
// Defer the operation by creating an indexed access type.
Expand Down Expand Up @@ -27343,7 +27344,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {

function getTypeFactsWorker(type: Type, callerOnlyNeeds: TypeFacts): TypeFacts {
if (type.flags & (TypeFlags.Intersection | TypeFlags.Instantiable)) {
type = getBaseConstraintOfType(type) || unknownType;
const constraintType = getBaseConstraintOfType(type) || unknownType;
if (strictNullChecks && type.flags & TypeFlags.Instantiable && constraintType === unknownType) {
return callerOnlyNeeds;
}
type = constraintType;
}
const flags = type.flags;
if (flags & (TypeFlags.String | TypeFlags.StringMapping)) {
Expand Down Expand Up @@ -27477,7 +27482,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// For each constituent type that can compare equal to the target nullable, intersect with the above union
// if the type doesn't already include the opppsite nullable and the constituent can compare equal to the
// opposite nullable; otherwise, just intersect with {}.
return mapType(type, t => hasTypeFacts(t, targetFacts) ? getIntersectionType([t, !(facts & otherIncludesFacts) && hasTypeFacts(t, otherFacts) ? emptyAndOtherUnion : emptyObjectType]) : t);
return mapType(type, t => hasTypeFacts(t, targetFacts) ? getIntersectionType([t, (strictNullChecks || !(facts & otherIncludesFacts)) && hasTypeFacts(t, otherFacts) ? emptyAndOtherUnion : emptyObjectType]) : t);
}

function recombineUnknownType(type: Type) {
Expand Down Expand Up @@ -34556,8 +34561,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}

function checkIndexedAccess(node: ElementAccessExpression, checkMode: CheckMode | undefined): Type {
return node.flags & NodeFlags.OptionalChain ? checkElementAccessChain(node as ElementAccessChain, checkMode) :
checkElementAccessExpression(node, checkNonNullExpression(node.expression), checkMode);
if (node.flags & NodeFlags.OptionalChain) {
return checkElementAccessChain(node as ElementAccessChain, checkMode);
}
if (shouldDeferIndexType(getTypeOfNode(node.argumentExpression))) {
return checkElementAccessExpression(node, checkExpression(node.expression), checkMode, /*deferredIndexCheck*/ true);
}
return checkElementAccessExpression(node, checkNonNullExpression(node.expression), checkMode);
}

function checkElementAccessChain(node: ElementAccessChain, checkMode: CheckMode | undefined) {
Expand All @@ -34566,7 +34576,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return propagateOptionalTypeMarker(checkElementAccessExpression(node, checkNonNullType(nonOptionalType, node.expression), checkMode), node, nonOptionalType !== exprType);
}

function checkElementAccessExpression(node: ElementAccessExpression, exprType: Type, checkMode: CheckMode | undefined): Type {
function checkElementAccessExpression(node: ElementAccessExpression, exprType: Type, checkMode: CheckMode | undefined, deferredIndexCheck: boolean = false): Type {
const objectType = getAssignmentTargetKind(node) !== AssignmentKind.None || isMethodAccessForCall(node) ? getWidenedType(exprType) : exprType;
const indexExpression = node.argumentExpression;
const indexType = checkExpression(indexExpression);
Expand All @@ -34593,7 +34603,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
}
const indexedAccessType = getIndexedAccessTypeOrUndefined(objectType, effectiveIndexType, accessFlags, node) || errorType;
return checkIndexedAccessIndexType(getFlowTypeOfAccessExpression(node, getNodeLinks(node).resolvedSymbol, indexedAccessType, indexExpression, checkMode), node);
return checkIndexedAccessIndexType(getFlowTypeOfAccessExpression(node, getNodeLinks(node).resolvedSymbol, indexedAccessType, indexExpression, checkMode), node, deferredIndexCheck);
}

function callLikeExpressionMayHaveTypeArguments(node: CallLikeExpression): node is CallExpression | NewExpression | TaggedTemplateExpression | JsxOpeningElement {
Expand Down Expand Up @@ -41556,7 +41566,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
getTypeFromTypeNode(node);
}

function checkIndexedAccessIndexType(type: Type, accessNode: IndexedAccessTypeNode | ElementAccessExpression) {
function checkIndexedAccessIndexType(type: Type, accessNode: IndexedAccessTypeNode | ElementAccessExpression, deferredIndexCheck: boolean = false) {
if (!(type.flags & TypeFlags.IndexedAccess)) {
return type;
}
Expand Down Expand Up @@ -41587,6 +41597,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
}
}
if (deferredIndexCheck && isAccessExpression(accessNode) && isErrorType(checkNonNullExpression(accessNode.expression))) {
return errorType;
}
error(accessNode, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(indexType), typeToString(objectType));
return errorType;
}
Expand Down
2 changes: 1 addition & 1 deletion src/harness/fourslashImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1738,7 +1738,7 @@ export class TestState {

for (const key in actual) {
if (ts.hasProperty(actual as any, key)) {
const ak = actual[key], ek = expected[key];
const ak = actual[key], ek = (expected as typeof actual)[key];
if (typeof ak === "object" && typeof ek === "object") {
recur(ak, ek, path ? path + "." + key : key);
}
Expand Down
5 changes: 4 additions & 1 deletion tests/baselines/reference/controlFlowGenericTypes.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ controlFlowGenericTypes.ts(90,44): error TS2355: A function whose declared type
controlFlowGenericTypes.ts(91,11): error TS2339: Property 'foo' does not exist on type 'MyUnion'.
Property 'foo' does not exist on type 'AA'.
controlFlowGenericTypes.ts(156,16): error TS18048: 'obj' is possibly 'undefined'.
controlFlowGenericTypes.ts(156,16): error TS2536: Type 'K' cannot be used to index type 'Record<keyof T, string> | undefined'.
controlFlowGenericTypes.ts(167,9): error TS18048: 'iSpec' is possibly 'undefined'.
controlFlowGenericTypes.ts(168,9): error TS18048: 'iSpec' is possibly 'undefined'.


==== controlFlowGenericTypes.ts (8 errors) ====
==== controlFlowGenericTypes.ts (9 errors) ====
function f1<T extends string | undefined>(x: T, y: { a: T }, z: [T]): string {
if (x) {
x;
Expand Down Expand Up @@ -181,6 +182,8 @@ controlFlowGenericTypes.ts(168,9): error TS18048: 'iSpec' is possibly 'undefined
const x1 = obj[key]; // Error
~~~
!!! error TS18048: 'obj' is possibly 'undefined'.
~~~~~~~~
!!! error TS2536: Type 'K' cannot be used to index type 'Record<keyof T, string> | undefined'.
const x2 = obj && obj[key];
}

Expand Down
8 changes: 4 additions & 4 deletions tests/baselines/reference/controlFlowGenericTypes.types
Original file line number Diff line number Diff line change
Expand Up @@ -662,10 +662,10 @@ function fx3<T extends Record<keyof T, string> | undefined, K extends keyof T>(o
> : ^

const x1 = obj[key]; // Error
>x1 : Record<keyof T, string>[K]
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^
>obj[key] : Record<keyof T, string>[K]
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^
>x1 : any
> : ^^^
>obj[key] : any
> : ^^^
>obj : Record<keyof T, string> | undefined
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>key : K
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
genericUnboundedTypeParamAssignability.ts(2,5): error TS2339: Property 'toString' does not exist on type 'T'.
genericUnboundedTypeParamAssignability.ts(2,3): error TS18049: 'o' is possibly 'null' or 'undefined'.
genericUnboundedTypeParamAssignability.ts(15,6): error TS2345: Argument of type 'T' is not assignable to parameter of type '{}'.
genericUnboundedTypeParamAssignability.ts(16,6): error TS2345: Argument of type 'T' is not assignable to parameter of type 'Record<string, any>'.
genericUnboundedTypeParamAssignability.ts(17,5): error TS2339: Property 'toString' does not exist on type 'T'.
genericUnboundedTypeParamAssignability.ts(17,3): error TS18049: 't' is possibly 'null' or 'undefined'.


==== genericUnboundedTypeParamAssignability.ts (4 errors) ====
function f1<T>(o: T) {
o.toString(); // error
~~~~~~~~
!!! error TS2339: Property 'toString' does not exist on type 'T'.
~
!!! error TS18049: 'o' is possibly 'null' or 'undefined'.
}

function f2<T extends {}>(o: T) {
Expand All @@ -30,7 +30,7 @@ genericUnboundedTypeParamAssignability.ts(17,5): error TS2339: Property 'toStrin
!!! error TS2345: Argument of type 'T' is not assignable to parameter of type 'Record<string, any>'.
!!! related TS2208 genericUnboundedTypeParamAssignability.ts:13:15: This type parameter might need an `extends Record<string, any>` constraint.
t.toString(); // error, for the same reason as f1()
~~~~~~~~
!!! error TS2339: Property 'toString' does not exist on type 'T'.
~
!!! error TS18049: 't' is possibly 'null' or 'undefined'.
}

Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ function f1<T>(o: T) {
>T : Symbol(T, Decl(genericUnboundedTypeParamAssignability.ts, 0, 12))

o.toString(); // error
>o.toString : Symbol(Object.toString, Decl(lib.es5.d.ts, --, --))
>o : Symbol(o, Decl(genericUnboundedTypeParamAssignability.ts, 0, 15))
>toString : Symbol(Object.toString, Decl(lib.es5.d.ts, --, --))
}

function f2<T extends {}>(o: T) {
Expand Down Expand Up @@ -55,6 +57,8 @@ function user<T>(t: T) {
>t : Symbol(t, Decl(genericUnboundedTypeParamAssignability.ts, 12, 17))

t.toString(); // error, for the same reason as f1()
>t.toString : Symbol(Object.toString, Decl(lib.es5.d.ts, --, --))
>t : Symbol(t, Decl(genericUnboundedTypeParamAssignability.ts, 12, 17))
>toString : Symbol(Object.toString, Decl(lib.es5.d.ts, --, --))
}

Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ function f1<T>(o: T) {
> : ^

o.toString(); // error
>o.toString() : any
> : ^^^
>o.toString : any
> : ^^^
>o.toString() : string
> : ^^^^^^
>o.toString : () => string
> : ^^^^^^
>o : T
> : ^
>toString : any
> : ^^^
>toString : () => string
> : ^^^^^^
}

function f2<T extends {}>(o: T) {
Expand Down Expand Up @@ -83,13 +83,13 @@ function user<T>(t: T) {
> : ^

t.toString(); // error, for the same reason as f1()
>t.toString() : any
> : ^^^
>t.toString : any
> : ^^^
>t.toString() : string
> : ^^^^^^
>t.toString : () => string
> : ^^^^^^
>t : T
> : ^
>toString : any
> : ^^^
>toString : () => string
> : ^^^^^^
}

Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ inKeywordTypeguard.ts(90,5): error TS2564: Property 'a' has no initializer and i
inKeywordTypeguard.ts(94,26): error TS2339: Property 'a' does not exist on type 'never'.
inKeywordTypeguard.ts(155,16): error TS18046: 'x' is of type 'unknown'.
inKeywordTypeguard.ts(158,21): error TS2638: Type '{}' may represent a primitive value, which is not permitted as the right operand of the 'in' operator.
inKeywordTypeguard.ts(183,16): error TS2322: Type 'T' is not assignable to type 'object'.
inKeywordTypeguard.ts(183,16): error TS18049: 'x' is possibly 'null' or 'undefined'.
inKeywordTypeguard.ts(186,21): error TS2638: Type 'NonNullable<T>' may represent a primitive value, which is not permitted as the right operand of the 'in' operator.


Expand Down Expand Up @@ -277,8 +277,7 @@ inKeywordTypeguard.ts(186,21): error TS2638: Type 'NonNullable<T>' may represent
function f3<T>(x: T) {
if ("a" in x) {
~
!!! error TS2322: Type 'T' is not assignable to type 'object'.
!!! related TS2208 inKeywordTypeguard.ts:182:13: This type parameter might need an `extends object` constraint.
!!! error TS18049: 'x' is possibly 'null' or 'undefined'.
x.a;
}
if (x && "a" in x) {
Expand Down
Loading