@@ -858,7 +858,8 @@ namespace ts {
858
858
emptyTypeLiteralSymbol.members = createSymbolTable();
859
859
const emptyTypeLiteralType = createAnonymousType(emptyTypeLiteralSymbol, emptySymbols, emptyArray, emptyArray, emptyArray);
860
860
861
- const unknownUnionType = strictNullChecks ? getUnionType([undefinedType, nullType, createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, emptyArray)]) : unknownType;
861
+ const unknownEmptyObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, emptyArray);
862
+ const unknownUnionType = strictNullChecks ? getUnionType([undefinedType, nullType, unknownEmptyObjectType]) : unknownType;
862
863
863
864
const emptyGenericType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, emptyArray) as ObjectType as GenericType;
864
865
emptyGenericType.instantiations = new Map<string, TypeReference>();
@@ -998,6 +999,7 @@ namespace ts {
998
999
let deferredGlobalOmitSymbol: Symbol | undefined;
999
1000
let deferredGlobalAwaitedSymbol: Symbol | undefined;
1000
1001
let deferredGlobalBigIntType: ObjectType | undefined;
1002
+ let deferredGlobalRecordSymbol: Symbol | undefined;
1001
1003
1002
1004
const allPotentiallyUnusedIdentifiers = new Map<Path, PotentiallyUnusedIdentifier[]>(); // key is file name
1003
1005
@@ -14314,6 +14316,11 @@ namespace ts {
14314
14316
return (deferredGlobalBigIntType ||= getGlobalType("BigInt" as __String, /*arity*/ 0, /*reportErrors*/ false)) || emptyObjectType;
14315
14317
}
14316
14318
14319
+ function getGlobalRecordSymbol(): Symbol | undefined {
14320
+ deferredGlobalRecordSymbol ||= getGlobalTypeAliasSymbol("Record" as __String, /*arity*/ 2, /*reportErrors*/ true) || unknownSymbol;
14321
+ return deferredGlobalRecordSymbol === unknownSymbol ? undefined : deferredGlobalRecordSymbol;
14322
+ }
14323
+
14317
14324
/**
14318
14325
* Instantiates a global type that is generic with some element type, and returns that instantiation.
14319
14326
*/
@@ -25199,19 +25206,27 @@ namespace ts {
25199
25206
25200
25207
function isTypePresencePossible(type: Type, propName: __String, assumeTrue: boolean) {
25201
25208
const prop = getPropertyOfType(type, propName);
25202
- if (prop) {
25203
- return prop.flags & SymbolFlags.Optional ? true : assumeTrue;
25204
- }
25205
- return getApplicableIndexInfoForName(type, propName) ? true : !assumeTrue;
25209
+ return prop ?
25210
+ !!(prop.flags & SymbolFlags.Optional) || assumeTrue :
25211
+ !!getApplicableIndexInfoForName(type, propName) || !assumeTrue;
25206
25212
}
25207
25213
25208
- function narrowByInKeyword(type: Type, name: __String, assumeTrue: boolean) {
25209
- if (type.flags & TypeFlags.Union
25210
- || type.flags & TypeFlags.Object && declaredType !== type && !(declaredType === unknownType && isEmptyAnonymousObjectType(type))
25211
- || isThisTypeParameter(type)
25212
- || type.flags & TypeFlags.Intersection && every((type as IntersectionType).types, t => t.symbol !== globalThisSymbol)) {
25214
+ function narrowByInKeyword(type: Type, nameType: StringLiteralType | NumberLiteralType | UniqueESSymbolType, assumeTrue: boolean) {
25215
+ const name = getPropertyNameFromType(nameType);
25216
+ const isKnownProperty = someType(type, t => isTypePresencePossible(t, name, /*assumeTrue*/ true));
25217
+ if (isKnownProperty) {
25218
+ // If the check is for a known property (i.e. a property declared in some constituent of
25219
+ // the target type), we filter the target type by presence of absence of the property.
25213
25220
return filterType(type, t => isTypePresencePossible(t, name, assumeTrue));
25214
25221
}
25222
+ if (assumeTrue) {
25223
+ // If the check is for an unknown property, we intersect the target type with `Record<X, unknown>`,
25224
+ // where X is the name of the property.
25225
+ const recordSymbol = getGlobalRecordSymbol();
25226
+ if (recordSymbol) {
25227
+ return getIntersectionType([type, getTypeAliasInstantiation(recordSymbol, [nameType, unknownType])]);
25228
+ }
25229
+ }
25215
25230
return type;
25216
25231
}
25217
25232
@@ -25271,15 +25286,14 @@ namespace ts {
25271
25286
return narrowTypeByPrivateIdentifierInInExpression(type, expr, assumeTrue);
25272
25287
}
25273
25288
const target = getReferenceCandidate(expr.right);
25274
- const leftType = getTypeOfNode(expr.left);
25275
- if (leftType.flags & TypeFlags.StringLiteral) {
25276
- const name = escapeLeadingUnderscores((leftType as StringLiteralType).value);
25289
+ const leftType = getTypeOfExpression(expr.left);
25290
+ if (leftType.flags & TypeFlags.StringOrNumberLiteralOrUnique) {
25277
25291
if (containsMissingType(type) && isAccessExpression(reference) && isMatchingReference(reference.expression, target) &&
25278
- getAccessedPropertyName(reference) === name ) {
25292
+ getAccessedPropertyName(reference) === getPropertyNameFromType(leftType as StringLiteralType | NumberLiteralType | UniqueESSymbolType) ) {
25279
25293
return getTypeWithFacts(type, assumeTrue ? TypeFacts.NEUndefined : TypeFacts.EQUndefined);
25280
25294
}
25281
25295
if (isMatchingReference(reference, target)) {
25282
- return narrowByInKeyword(type, name , assumeTrue);
25296
+ return narrowByInKeyword(type, leftType as StringLiteralType | NumberLiteralType | UniqueESSymbolType , assumeTrue);
25283
25297
}
25284
25298
}
25285
25299
break;
@@ -33848,6 +33862,10 @@ namespace ts {
33848
33862
return booleanType;
33849
33863
}
33850
33864
33865
+ function hasEmptyObjectIntersection(type: Type): boolean {
33866
+ return someType(type, t => t === unknownEmptyObjectType || !!(t.flags & TypeFlags.Intersection) && some((t as IntersectionType).types, isEmptyAnonymousObjectType));
33867
+ }
33868
+
33851
33869
function checkInExpression(left: Expression, right: Expression, leftType: Type, rightType: Type): Type {
33852
33870
if (leftType === silentNeverType || rightType === silentNeverType) {
33853
33871
return silentNeverType;
@@ -33864,43 +33882,20 @@ namespace ts {
33864
33882
}
33865
33883
}
33866
33884
else {
33867
- leftType = checkNonNullType(leftType, left);
33868
- // TypeScript 1.0 spec (April 2014): 4.15.5
33869
- // Require the left operand to be of type Any, the String primitive type, or the Number primitive type.
33870
- if (!(allTypesAssignableToKind(leftType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbolLike) ||
33871
- isTypeAssignableToKind(leftType, TypeFlags.Index | TypeFlags.TemplateLiteral | TypeFlags.StringMapping | TypeFlags.TypeParameter))) {
33872
- error(left, Diagnostics.The_left_hand_side_of_an_in_expression_must_be_a_private_identifier_or_of_type_any_string_number_or_symbol);
33885
+ // The type of the lef operand must be assignable to string, number, or symbol.
33886
+ checkTypeAssignableTo(checkNonNullType(leftType, left), stringNumberSymbolType, left);
33887
+ }
33888
+ // The type of the right operand must be assignable to 'object'.
33889
+ if (checkTypeAssignableTo(checkNonNullType(rightType, right), nonPrimitiveType, right)) {
33890
+ // The {} type is assignable to the object type, yet {} might represent a primitive type. Here we
33891
+ // detect and error on {} that results from narrowing the unknown type, as well as intersections
33892
+ // that include {} (we know that the other types in such intersections are assignable to object
33893
+ // since we already checked for that).
33894
+ if (hasEmptyObjectIntersection(rightType)) {
33895
+ error(right, Diagnostics.Type_0_may_represent_a_primitive_value_which_is_not_permitted_as_the_right_operand_of_the_in_operator, typeToString(rightType));
33873
33896
}
33874
33897
}
33875
- rightType = checkNonNullType(rightType, right);
33876
- // TypeScript 1.0 spec (April 2014): 4.15.5
33877
- // The in operator requires the right operand to be
33878
- //
33879
- // 1. assignable to the non-primitive type,
33880
- // 2. an unconstrained type parameter,
33881
- // 3. a union or intersection including one or more type parameters, whose constituents are all assignable to the
33882
- // the non-primitive type, or are unconstrainted type parameters, or have constraints assignable to the
33883
- // non-primitive type, or
33884
- // 4. a type parameter whose constraint is
33885
- // i. an object type,
33886
- // ii. the non-primitive type, or
33887
- // iii. a union or intersection with at least one constituent assignable to an object or non-primitive type.
33888
- //
33889
- // The divergent behavior for type parameters and unions containing type parameters is a workaround for type
33890
- // parameters not being narrowable. If the right operand is a concrete type, we can error if there is any chance
33891
- // it is a primitive. But if the operand is a type parameter, it cannot be narrowed, so we don't issue an error
33892
- // unless *all* instantiations would result in an error.
33893
- //
33894
33898
// The result is always of the Boolean primitive type.
33895
- const rightTypeConstraint = getConstraintOfType(rightType);
33896
- if (!allTypesAssignableToKind(rightType, TypeFlags.NonPrimitive | TypeFlags.InstantiableNonPrimitive) ||
33897
- rightTypeConstraint && (
33898
- isTypeAssignableToKind(rightType, TypeFlags.UnionOrIntersection) && !allTypesAssignableToKind(rightTypeConstraint, TypeFlags.NonPrimitive | TypeFlags.InstantiableNonPrimitive) ||
33899
- !maybeTypeOfKind(rightTypeConstraint, TypeFlags.NonPrimitive | TypeFlags.InstantiableNonPrimitive | TypeFlags.Object)
33900
- )
33901
- ) {
33902
- error(right, Diagnostics.The_right_hand_side_of_an_in_expression_must_not_be_a_primitive);
33903
- }
33904
33899
return booleanType;
33905
33900
}
33906
33901
0 commit comments